Attach the tarballs before the Release publishes, because published means frozen - #165
Attach the tarballs before the Release publishes, because published means frozen#165wmadden-electric wants to merge 14 commits into
Conversation
…e + rc1, STOPs open The slice contract for S7: mount the ORM family, make the grammar completeness check fail the build, create the prisma package, commit all product pins, and extend publish.yml to emit and verify the rc1 artifacts. Eight operator questions are open (trigger shape, the one action, package shape, exception-list ratification, S6 wiring, S8 ordering, pin convergence, publish credentials); nothing dispatches until they are ruled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…is one binary Applies the operator's two rulings (2026-08-12): rc1 ships as @prisma/cli under the existing prisma-cli bin, with the bare prisma name a follow-up; the slice's goal is combining all available commands into one binary, with exception-list reconciliation also deferred. Closes STOP-2/3/4/8; records working defaults so D1/D2 proceed while STOP-1/5/6/7 stay open. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
… of a prisma package Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
`prisma migration list`, `prisma db verify`, `prisma init` and the rest of the ORM family existed in no binary this repo shipped. They were published in @prisma/orm-toolchain, mounted nowhere. This mounts the family in the v8 shell, so one binary answers platform, composer and ORM commands. The family keys its commands by the path they mount at, so the mount is its own map spread in whole; its `orm` config section, docs base and redirect table ride the family object, with nothing wired per command. Four group briefs (contract, db, migration, ref) join cliGroups. The dependency is exact and committed at 8.0.0-rc.1-dev.40, the interim version the contract's working default names. It pins @prisma/cli-engine 0.0.9 while this workspace ships 8.0.0-rc.1, the same second engine copy composer's pin already installs; both close when the rc lines converge. Tests: the mount-coverage check gains the family and its 22 paths, and a new v8-orm-mount suite runs `migration list` end to end against a fixture project (exit 0, real result), proves the config section is validated before a command runs, proves `migration apply` settles as the family's typed redirect, and proves the root help names the new groups. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The mount-coverage check ran in the test suite only, and the publish path runs `pnpm build` plus `pnpm test:scripts` — so a tree missing a command could be packed and published without that check ever seeing it. It is now `pnpm check:grammar`: the same vitest file, run as a turbo task that builds its dependencies first and is never cached, wired into pr-quality.yml as its own required job and into publish.yml before the first publish step, under the same publish == 'true' condition its neighbours carry. Nothing is reimplemented; the check that guards the tree in tests is the check that guards the release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
An 8.x publish whose declared bin was the retiring commander shell would misdescribe itself: `prisma-cli` resolved dist/cli.js, which answers no composer and no ORM command. The bin map now points at dist/v8/cli.js — the tree the grammar check guards. The legacy entry keeps building and shipping inside the tarball; deleting it is S2d. The new e2e test starts from the manifest rather than a hard-coded path: it reads the declared bin, runs it with --version on plain Node in a bare environment, and asserts the lockstep version comes back in the result envelope at exit 0. Piped stdout is not a TTY, so the shell answers in its JSON stream, and the test asserts the frame. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Operator review of the ORM mount: spreading the family's own command map made the family, not the shell, the source of truth for where its commands mount — against R12. The 22 mounts are now written out per path, like every other family's. Writing them out also put the ORM paths in front of the e2e-coverage scan for the first time (it reads the source text, and a spread has no keys to read). They are excluded with the reason the convention asks for: ORM commands never call the management API this suite covers — their real e2e lives in prisma/prisma per R7, and the shell proves composition in v8-orm-mount.test.ts per R8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The publish workflow built, checked the grammar, and published. Nothing between those steps ever installed the tarball the way a user would, so a packed artifact that could not start outside the workspace would have shipped without any check noticing. The pipeline now packs both publishable tarballs and installs the CLI's in a sandbox outside the repo: npm with --ignore-scripts (the runner holds id-token: write; third-party postinstalls do not run at the pipeline's most privileged moment), the unpublished engine resolved through a computed npm override with an absolute file: path and a version-qualified key, and every declared bin started on plain Node at exit 0 — which since the bin flip means the v8 tree, so the start crosses the composer and ORM import boundaries. The verified tarballs upload as workflow artifacts and attach to the GitHub Release, so what was verified is what is retrievable. The dry-run dispatch exercises all of it without registry writes. The override computation is a pure function with its own tests, run by test:scripts. The mechanics follow S6's check-3b design (ruled 2026-08-12: S7 carries the check itself while S6 lands in parallel), so the conformance slice absorbs this as a move, not a rewrite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ipeline Operator rulings 2026-08-12: the existing publish mechanisms stand (STOP-1); S7 carries the install smoke while S6 lands in parallel (STOP-5); pin convergence waits until 8.0.0-rc.1 publishes (STOP-7). STOP-6 closed by events - #162 merged first and this branch carries the merge. versioning.md documents the verification stage between the version decision and the registry; plan.md and deferred.md record where S7 stands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Operator ruling 2026-08-12: merging an RC-line bump PR must not move latest - the pre-v8 CLI keeps serving bare installs until the line is ready. Each release now publishes under its version's canonical dist-tag, decided by one pure function: next for 8.0.0-rc.N, latest for stable. The GitHub Release (with the smoked tarballs attached) follows releases rather than the latest tag, so RC releases get their prerelease Release too. The dispatch input's default changes from latest to empty, which resolves to the canonical tag - so a routine re-publish dispatch of an RC can never move latest by accident. Passing latest explicitly for an RC version is the deliberate cutover act, and the input says so. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…the rulings Review findings (CodeRabbit). The substantive one: a workflow rerun or re-publish dispatch met npm's refusal to publish over an existing version and died before the Release step could repair a missing Release or its assets. The publish step now treats an already-published version as done and still fails on every other error. The rest are record consistency: the rollout table's @prisma/cli row follows the next-tag ruling; the spec and dispatch plan are marked revision 3 with the final STOP dispositions instead of the working defaults they were drafted against; two markdownlint complaints (heading levels, a line starting with #162) fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…eans frozen The first real next publish reached the Release step and failed there: this repo's releases are immutable, so gh release create published v8.0.0-rc.1 and the follow-up asset upload was refused with HTTP 422. The step now creates the Release as a draft with the smoked tarballs already attached, then publishes it through the API by the draft's id (a draft's tag does not exist yet, so gh cannot address it by tag). A rerun that finds the Release already published reports there is nothing to repair and succeeds, instead of failing the run after a complete npm publish. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
|
Warning Review limit reached
Next review available in: 47 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Summary by CodeRabbit
WalkthroughThe v8 CLI now mounts the ORM command family and validates command completeness. The shipped 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…r path Operator ruling 2026-08-12: cosmetic, not immediate. npm is healthy; only the GitHub Release page is missing its tarballs, permanently, because it published before the upload and releases are immutable. The repair steps (merge #165, attempt deletion, re-dispatch) are in the entry for whoever picks it up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.drive/projects/prisma-cli-v8/plan.md:
- Line 126: Update the release-status entry in plan.md to record that the
immutable v8.0.0-rc.1 Release has no assets and cannot be modified or recreated.
Clarify that future Releases will have tarball assets attached while still in
draft, while preserving the existing shipped-work summary.
In @.drive/projects/prisma-cli-v8/specs/s7-release.md:
- Around line 15-16: Update the stale STOP status across the three cited sites:
in .drive/projects/prisma-cli-v8/specs/s7-release.md lines 15-16, replace the
present-tense open-STOP statement with the final closed/deferred status; in
.drive/projects/prisma-cli-v8/plans/s7-release.md lines 16-19, mark the ordering
statement as historical or revise it for the closed STOPs; and in lines 78-80,
remove the obsolete STOP-1 blocker statement.
- Around line 174-188: The D5 release contract and plan must document the
immutable GitHub Release lifecycle: update
.drive/projects/prisma-cli-v8/specs/s7-release.md lines 174-188 and
.drive/projects/prisma-cli-v8/plans/s7-release.md lines 78-96 to require
creating a draft Release, attaching tarballs, and publishing npm packages by
Release ID; update docs/oss/versioning.md lines 58-69 to state that reruns may
create a missing Release but must not modify a published Release, while
retaining the existing deferred.md record for the assetless v8.0.0-rc.1 Release.
In @.github/workflows/publish.yml:
- Around line 210-235: Update the release handling around gh release view and
release_id so an existing matching release is queried for its draft status
rather than treated as complete unconditionally. Exit only when the release is
published; when a draft exists, reuse its ID, publish it, and reconcile assets
only if necessary instead of creating another release.
In `@packages/cli/tests/e2e-coverage.test.ts`:
- Around line 33-51: Update mountedCommands() to recognize both quoted and
unquoted object keys so format, init, lsp, migrate, and feedback are included in
the coverage scan. Then add e2e coverage for any newly detected ORM commands or
add justified entries to EXCLUSIONS for those that remain intentionally
excluded.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 4860a1b1-ab73-4463-8572-b9a7d3cb95bd
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (26)
.drive/projects/prisma-cli-v8/assets/rollout-plan.md.drive/projects/prisma-cli-v8/assets/s2/parity-divergences-s7.md.drive/projects/prisma-cli-v8/deferred.md.drive/projects/prisma-cli-v8/plan.md.drive/projects/prisma-cli-v8/plans/s7-release.md.drive/projects/prisma-cli-v8/specs/s7-release.md.github/workflows/pr-quality.yml.github/workflows/publish.yml.gitignoredocs/oss/versioning.mdpackage.jsonpackages/cli/e2e/declared-bin.e2e.tspackages/cli/package.jsonpackages/cli/src/v8/cli.tspackages/cli/tests/e2e-coverage.test.tspackages/cli/tests/fixtures/v8-orm-project/migrations/.gitkeeppackages/cli/tests/v8-mount-coverage.test.tspackages/cli/tests/v8-orm-mount.test.tspnpm-workspace.yamlscripts/determine-version-utils.test.tsscripts/determine-version-utils.tsscripts/determine-version.tsscripts/tarball-smoke-utils.mjsscripts/tarball-smoke-utils.test.mjsscripts/tarball-smoke.mjsturbo.json
| release automation, pinned product versions, and the pipeline emitting | ||
| a publishable `prisma@8.0.0-rc1` artifact from a tagged commit. Ends | ||
| when the operator can publish with one action (project DoD). | ||
| Repo: prisma-cli. **In flight (PR #164).** Ruled 2026-08-12: rc1 publishes under the existing names (`@prisma/cli`, bin `prisma-cli`); the bare-`prisma` cutover follows once `prisma7` frees the name. Shipped so far: the ORM family mounted (one binary answers platform, composer and ORM), the grammar check promoted to `pnpm check:grammar` running in `pr-quality.yml` and before every publish, the declared bin flipped to the v8 tree, and the tarball install smoke in the publish path (packed tarballs verified out-of-workspace on plain Node, uploaded as artifacts, attached to the Release). The operator's one action is merging the bump PR, per `docs/oss/versioning.md`. Engine-pin convergence is deferred until `8.0.0-rc.1` publishes (contract STOP-7). |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Record the immutable v8.0.0-rc.1 Release exception.
Line 126 says tarballs are attached to the Release. The existing v8.0.0-rc.1 Release has no assets and cannot be modified or recreated. State that future Releases attach assets while draft, and record this exception.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.drive/projects/prisma-cli-v8/plan.md at line 126, Update the release-status
entry in plan.md to record that the immutable v8.0.0-rc.1 Release has no assets
and cannot be modified or recreated. Clarify that future Releases will have
tarball assets attached while still in draft, while preserving the existing
shipped-work summary.
| implementation. STOP-1 and STOP-5…7 remain open for the release-side | ||
| deliverables; D1/D2 proceed on the recorded working defaults. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Remove or qualify stale STOP status.
These statements present STOPs as currently open even though the same records state that all STOPs are closed or deferred. This can cause operators to follow superseded release instructions.
.drive/projects/prisma-cli-v8/specs/s7-release.md#L15-L16: replace the present-tense open-STOP statement with the final status..drive/projects/prisma-cli-v8/plans/s7-release.md#L16-L19: mark the ordering statement as historical, or update it for the closed STOPs..drive/projects/prisma-cli-v8/plans/s7-release.md#L78-L80: remove the obsolete STOP-1 blocker statement.
📍 Affects 2 files
.drive/projects/prisma-cli-v8/specs/s7-release.md#L15-L16(this comment).drive/projects/prisma-cli-v8/plans/s7-release.md#L16-L19.drive/projects/prisma-cli-v8/plans/s7-release.md#L78-L80
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.drive/projects/prisma-cli-v8/specs/s7-release.md around lines 15 - 16,
Update the stale STOP status across the three cited sites: in
.drive/projects/prisma-cli-v8/specs/s7-release.md lines 15-16, replace the
present-tense open-STOP statement with the final closed/deferred status; in
.drive/projects/prisma-cli-v8/plans/s7-release.md lines 16-19, mark the ordering
statement as historical or revise it for the closed STOPs; and in lines 78-80,
remove the obsolete STOP-1 blocker statement.
| **D5 — The pipeline: release commit → verified artifact → one action.** | ||
| Per STOP-1's ruling on trigger shape. Written against the recommendation | ||
| (STOP-1a): `publish.yml` gains an artifact-emission stage — after `pnpm | ||
| build` and the grammar check, it packs the engine and cli tarballs with | ||
| `pnpm pack`, runs the tarball smoke (S6 check 3b mechanics: | ||
| out-of-workspace install with computed absolute `file:` overrides, | ||
| `--ignore-scripts`, every declared bin starts on plain Node, exit 0, | ||
| under a timeout — which after D3 means the smoke exercises the v8 tree | ||
| and the composer/ORM family boundary), uploads the tarballs as workflow | ||
| artifacts, and attaches them to the GitHub Release it already creates. | ||
| The operator's one action for rc1 stays what versioning.md already | ||
| rules: merge the `chore(release)` bump PR. Everything after that push — | ||
| build, grammar check, smoke, npm publishes, Release + tag, artifact | ||
| upload — is the pipeline. Dry-run dispatch exercises all of it minus | ||
| registry writes. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- tracked candidate files ---'
git ls-files \
'.drive/projects/prisma-cli-v8/specs/s7-release.md' \
'.drive/projects/prisma-cli-v8/plans/s7-release.md' \
'docs/oss/versioning.md' \
'.github/workflows/publish.yml' \
'scripts' | sed -n '1,120p'
printf '%s\n' '--- relevant workflow and release references ---'
rg -n -i -C 4 \
'release|upload|artifact|dry.?run|publish-npm|determine-version|check:grammar|tarball-smoke|pnpm pack' \
.github/workflows scripts docs/oss .drive/projects/prisma-cli-v8/specs/s7-release.md .drive/projects/prisma-cli-v8/plans/s7-release.md \
2>/dev/null | sed -n '1,260p'
printf '%s\n' '--- target sections ---'
sed -n '1,115p' .drive/projects/prisma-cli-v8/plans/s7-release.md
sed -n '1,215p' .drive/projects/prisma-cli-v8/specs/s7-release.md
sed -n '45,85p' docs/oss/versioning.mdRepository: prisma/prisma-cli
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- publish.yml outline ---'
ast-grep outline .github/workflows/publish.yml
printf '%s\n' '--- publish.yml release and artifact logic ---'
rg -n -i -C 8 \
'release|upload|artifact|dry.?run|publish|determine-version|version' \
.github/workflows/publish.yml | sed -n '1,320p'
printf '%s\n' '--- exact contradictory status and D5 text ---'
nl -ba .drive/projects/prisma-cli-v8/specs/s7-release.md | sed -n '1,22p;174,190p;198,235p;276,305p'
nl -ba .drive/projects/prisma-cli-v8/plans/s7-release.md | sed -n '10,20p;78,97p'
nl -ba docs/oss/versioning.md | sed -n '55,85p'
printf '%s\n' '--- release-related action references ---'
rg -n -i -C 5 \
'softprops/action-gh-release|gh release|create.*release|upload.*release|release.*asset|draft|immutable|existing Release|re-creates' \
.github .drive docs scripts 2>/dev/null | sed -n '1,320p'Repository: prisma/prisma-cli
Length of output: 46196
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
workflow = Path(".github/workflows/publish.yml").read_text()
spec = Path(".drive/projects/prisma-cli-v8/specs/s7-release.md").read_text()
plan = Path(".drive/projects/prisma-cli-v8/plans/s7-release.md").read_text()
docs = Path("docs/oss/versioning.md").read_text()
deferred = Path(".drive/projects/prisma-cli-v8/deferred.md").read_text()
def section(text, start, end=None):
i = text.index(start)
j = text.index(end, i) if end else len(text)
return text[i:j]
d5 = section(spec, "**D5 —", "**D6 —")
plan_d5 = section(plan, "## D5 —", "## D6 —")
acceptance = section(spec, "## 5. Acceptance", "## 6. Out of scope")
checks = {
"workflow creates draft before assets and publishes by release id":
workflow.index("gh release create") < workflow.index("release_id=$(gh api") < workflow.index("gh api -X PATCH"),
"workflow passes tarballs to draft creation":
"artifacts/tarballs/*.tgz" in workflow[workflow.index("gh release create"):workflow.index("release_id=$(gh api")],
"workflow handles existing published release as success":
"already exists and releases are immutable" in workflow,
"spec D5 states draft":
bool(re.search(r"\bdraft\b", d5, re.I)),
"spec acceptance states draft or publish-by-id":
bool(re.search(r"\bdraft\b|publish(?:ed|ing)? by|release id", acceptance, re.I)),
"plan D5 states draft":
bool(re.search(r"\bdraft\b", plan_d5, re.I)),
"docs states published release cannot be modified":
bool(re.search(r"cannot|can't|immutable|cannot modify|cannot repair", docs, re.I)),
"docs states missing-release rerun behavior":
bool(re.search(r"re-creates.*Release|missing Release|missing.*release", docs, re.I)),
"deferred records rc1 assetless and immutable":
bool(re.search(r"v8\.0\.0-rc\.1.*no tarballs", deferred, re.I | re.S))
and bool(re.search(r"immutable", deferred, re.I)),
}
for name, result in checks.items():
print(f"{'PASS' if result else 'FAIL'}: {name}")
print("\nRelevant contract excerpts:")
for label, text in [("spec D5", d5), ("plan D5", plan_d5)]:
print(f"\n--- {label} ---\n{text.strip()}")
print("\n--- docs rerun paragraph ---")
print(docs[docs.index("If the publish needs to be re-run"):docs.index("\n\n", docs.index("If the publish needs to be re-run"))])
PYRepository: prisma/prisma-cli
Length of output: 3343
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
docs = Path("docs/oss/versioning.md").read_text().splitlines()
print("--- versioning.md lines containing release immutability terms ---")
for number, line in enumerate(docs, 1):
low = line.lower()
if any(term in low for term in ("immutable", "cannot", "can't", "repair", "missing release", "re-create")):
print(f"{number}: {line}")
print("\n--- deferred.md rc1 record ---")
deferred = Path(".drive/projects/prisma-cli-v8/deferred.md").read_text().splitlines()
for number, line in enumerate(deferred, 1):
if 7 <= number <= 25:
print(f"{number}: {line}")
print("\n--- exact documentation gap check ---")
paragraph = "\n".join(docs[67:70])
required_phrases = (
"draft",
"attach",
"publish",
"Release ID",
"already-published",
"cannot modify",
)
for phrase in required_phrases:
print(f"{'PASS' if phrase.lower() in paragraph.lower() else 'FAIL'}: docs rerun paragraph contains {phrase!r}")
PYRepository: prisma/prisma-cli
Length of output: 3226
Document the immutable GitHub Release lifecycle.
Add the existing workflow order—draft Release, attach tarballs, publish by Release ID—to the D5 contract and plan. State in docs/oss/versioning.md that reruns can create a missing Release but cannot modify a published Release. The existing deferred.md entry already records the assetless v8.0.0-rc.1 Release.
📍 Affects 3 files
.drive/projects/prisma-cli-v8/specs/s7-release.md#L174-L188(this comment).drive/projects/prisma-cli-v8/plans/s7-release.md#L78-L96docs/oss/versioning.md#L58-L69
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.drive/projects/prisma-cli-v8/specs/s7-release.md around lines 174 - 188,
The D5 release contract and plan must document the immutable GitHub Release
lifecycle: update .drive/projects/prisma-cli-v8/specs/s7-release.md lines
174-188 and .drive/projects/prisma-cli-v8/plans/s7-release.md lines 78-96 to
require creating a draft Release, attaching tarballs, and publishing npm
packages by Release ID; update docs/oss/versioning.md lines 58-69 to state that
reruns may create a missing Release but must not modify a published Release,
while retaining the existing deferred.md record for the assetless v8.0.0-rc.1
Release.
| if gh release view "v$VERSION" >/dev/null 2>&1; then | ||
| echo "Release v$VERSION already exists and releases are immutable — nothing to repair." | ||
| exit 0 | ||
| fi | ||
| PRERELEASE_FLAG="" | ||
| case "$VERSION" in | ||
| *-rc.*) PRERELEASE_FLAG="--prerelease" ;; | ||
| esac | ||
| if gh release view "v$VERSION" >/dev/null 2>&1; then | ||
| gh release edit "v$VERSION" \ | ||
| --target "$GITHUB_SHA" \ | ||
| --title "v$VERSION" | ||
| else | ||
| gh release create "v$VERSION" \ | ||
| --target "$GITHUB_SHA" \ | ||
| --title "v$VERSION" \ | ||
| --generate-notes \ | ||
| $PRERELEASE_FLAG | ||
| gh release create "v$VERSION" \ | ||
| --draft \ | ||
| --target "$GITHUB_SHA" \ | ||
| --title "v$VERSION" \ | ||
| --generate-notes \ | ||
| $PRERELEASE_FLAG \ | ||
| artifacts/tarballs/*.tgz | ||
| # A draft's tag does not exist yet, so `gh release edit | ||
| # <tag> --draft=false` cannot address it; publish through the | ||
| # API by the draft's id. | ||
| release_id=$(gh api "repos/$GITHUB_REPOSITORY/releases" \ | ||
| --jq ".[] | select(.draft and .tag_name == \"v$VERSION\") | .id" | head -1) | ||
| if [ -z "$release_id" ]; then | ||
| echo "Could not find the draft release for v$VERSION" >&2 | ||
| exit 1 | ||
| fi | ||
| gh api -X PATCH "repos/$GITHUB_REPOSITORY/releases/$release_id" \ | ||
| -F draft=false >/dev/null |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
: "${VERSION:?Set VERSION to the release version without the v prefix}"
echo "Release records for v$VERSION:"
gh api --paginate "repos/$GITHUB_REPOSITORY/releases?per_page=100" \
--jq ".[] | select(.tag_name == \"v$VERSION\") | {id, draft, published_at, assets: [.assets[].name]}"Repository: prisma/prisma-cli
Length of output: 234
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file=".github/workflows/publish.yml"
sed -n '180,245p' "$file"
printf '\nRelease-related references:\n'
rg -n -C 3 'gh release|releases|release_id|artifacts/tarballs|draft' "$file"Repository: prisma/prisma-cli
Length of output: 5656
🌐 Web query:
GitHub CLI gh release view draft release by tag behavior REST API GET release by tag draft
💡 Result:
The GitHub REST API endpoint GET /repos/{owner}/{repo}/releases/tags/{tag} is designed to retrieve published releases only [1][2]. It does not support retrieving draft releases, and attempting to use this endpoint for a draft release will typically result in a 404 Not Found error [3]. The GitHub CLI (gh) handles this limitation by using a fallback mechanism when you run gh release view <tag>. Because the standard REST API lookup fails for drafts, the CLI performs the following internal sequence [3]: 1. It initially attempts to fetch the release using the standard REST API endpoint (GET /repos/{owner}/{repo}/releases/tags/{tag}) [3]. 2. If that request returns a 404, the CLI falls back to using a GraphQL query to search for the release by tag within the repository [3]. 3. Once the release (including a draft) is located via GraphQL, the CLI retrieves the full release details using a subsequent REST API call to the individual release endpoint (GET /repos/{owner}/{repo}/releases/{id}) [3]. In summary, while the underlying REST API used to look up a release by its tag does not support drafts, the gh release view command is engineered to work around this by utilizing GraphQL and the release ID to successfully display draft release information, provided the user has sufficient permissions to see the draft [3]. Accessing draft releases requires appropriate repository permissions (typically write access) [4][1].
Citations:
- 1: https://docs.github.com/en/rest/releases/releases
- 2: https://docs.github.com/en/rest/releases/releases?apiVersion=2026-03-10&apiversion=2022-11-28%2F1000
- 3:
getReleaseByTagNamedoes not find draft releases hub4j/github-api#1983 - 4: Accessing draft releases is not possible using GITHUB_TOKEN in Actions cli/cli#3037
🌐 Web query:
GitHub CLI gh release create existing tag draft release duplicate behavior
💡 Result:
When using the GitHub CLI (gh) to create a release, the behavior regarding existing tags and draft releases depends on how the command is configured and the state of your repository [1][2]. Key behaviors include: Automatic Tag Creation: If you run gh release create and the specified git tag does not already exist in the remote repository, the command will automatically create the tag based on the latest state of the default branch [1][3]. You can use the --verify-tag flag to force the command to abort if the tag does not exist instead of creating it [1][3]. Draft Releases and Tag Association: When you create a release as a draft (using --draft), GitHub may associate it with an "untagged" state or a temporary hash until it is published [4]. This is standard behavior for draft releases, which do not necessarily need to be permanently bound to a git tag while still in draft mode [4]. If you later publish the draft, it will typically associate with the intended tag [4]. Duplicate/Existing Release Issues: Race Conditions: There are known reports of race conditions when users attempt to delete an existing release and tag (e.g., gh release delete --cleanup-tag) and immediately follow it with gh release create [5]. If the delete operation has not fully propagated on the GitHub platform, the subsequent create command may incorrectly create a draft release or fail to associate properly [5]. Inserting a brief delay between the delete and create commands is a common workaround [5]. Local vs. Remote Tags: If a tag exists locally but has not been pushed to the remote repository, gh release create will not automatically push your local tag [6]. Instead, the API may create a new tag on the remote side, which might lack specific annotations present in your local version [6]. It is recommended to push tags manually before running the release command to ensure consistency [6]. To avoid issues with duplicate or unintentional draft behavior, ensure that: 1. Tags are pushed to the remote repository before running the release command if you want to use existing local tags [6]. 2. You provide sufficient time for deletion operations to process on the server side if automating a "delete and recreate" workflow [5]. 3. You use --verify-tag if you want to strictly prevent the creation of new tags [1][3].
Citations:
- 1: https://cli.github.com/manual/gh_release_create
- 2: https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository?tool=cli
- 3: https://www.mankier.com/1/gh-release-create
- 4:
gh release create --draft --prereleasegoes untagged cli/cli#11589 - 5:
gh release createcreates draft cli/cli#8458 - 6: Should
gh release createpush the local tag if it exists? cli/cli#4357
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
curl -fsSL https://raw.githubusercontent.com/cli/cli/trunk/pkg/cmd/release/view/view.go -o "$tmp/view.go"
curl -fsSL https://raw.githubusercontent.com/cli/cli/trunk/pkg/cmd/release/create/create.go -o "$tmp/create.go"
printf '%s\n' '--- gh release view draft-related logic ---'
rg -n -C 8 'draft|GetReleaseByTag|GraphQL|release.*id|HTTPNotFound' "$tmp/view.go" || true
printf '%s\n' '--- gh release create draft/upload/publish-related logic ---'
rg -n -C 8 'Draft|draft|Upload|asset|CreateRelease|Publish' "$tmp/create.go" || trueRepository: prisma/prisma-cli
Length of output: 10004
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
curl -fsSL https://raw.githubusercontent.com/cli/cli/trunk/pkg/cmd/release/view/view.go -o "$tmp/view.go"
curl -fsSL https://raw.githubusercontent.com/cli/cli/trunk/pkg/cmd/release/shared/http.go -o "$tmp/http.go" || true
printf '%s\n' '--- view command implementation ---'
sed -n '1,180p' "$tmp/view.go"
printf '%s\n' '--- release lookup helpers in CLI source ---'
curl -fsSL https://api.github.com/repos/cli/cli/git/trees/trunk?recursive=1 |
jq -r '.tree[].path' |
rg 'pkg/cmd/release/.+\.go$' |
while read -r path; do
url="https://raw.githubusercontent.com/cli/cli/trunk/$path"
body="$(curl -fsSL "$url")"
if printf '%s' "$body" | rg -q 'GetReleaseByTag|release.*tag|IsDraft|draft'; then
printf '\n--- %s ---\n' "$path"
printf '%s' "$body" | rg -n -C 5 'GetReleaseByTag|publishedReleaseExists|IsDraft|draft'
fi
doneRepository: prisma/prisma-cli
Length of output: 4577
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
for path in \
pkg/cmd/release/shared/shared.go \
pkg/cmd/release/shared/release.go \
pkg/cmd/release/shared/fetch.go \
pkg/cmd/release/shared/http.go \
pkg/cmd/release/shared/api.go
do
url="https://raw.githubusercontent.com/cli/cli/trunk/$path"
if curl -fsSL "$url" -o "$tmp/$(basename "$path")"; then
printf '\n--- %s ---\n' "$path"
rg -n -C 8 'FetchRelease|GetRelease|releases/tags|graphql|IsDraft|draft' "$tmp/$(basename "$path")" || true
fi
done
printf '\n--- local state-machine probe ---\n'
python3 - <<'PY'
states = {
"published": {"view": 0, "create": 0, "publish": 0},
"draft": {"view": 0, "create": 1, "publish": 0},
"missing": {"view": 1, "create": 0, "publish": 1},
}
for name, s in states.items():
if s["view"] == 0:
outcome = "early success; draft remains unpublished" if name == "draft" else "early success"
elif s["create"] == 0:
outcome = "create then publish"
else:
outcome = "create failure"
print(f"{name}: {outcome}")
PYRepository: prisma/prisma-cli
Length of output: 5103
Resume an existing draft Release before creating a new one.
If gh release create --draft succeeds and the publish call fails, gh release view "v$VERSION" also finds the draft and exits successfully. The draft remains unpublished.
Query the matching Release and inspect draft. Exit only when the Release is published. If a matching draft exists, use its ID to publish it and reconcile assets only when required.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/publish.yml around lines 210 - 235, Update the release
handling around gh release view and release_id so an existing matching release
is queried for its draft status rather than treated as complete unconditionally.
Exit only when the release is published; when a draft exists, reuse its ID,
publish it, and reconcile assets only if necessary instead of creating another
release.
| const EXCLUSIONS: Readonly<Record<string, string>> = { | ||
| "contract emit": ORM_FAMILY_REASON, | ||
| "contract infer": ORM_FAMILY_REASON, | ||
| "db init": ORM_FAMILY_REASON, | ||
| "db schema": ORM_FAMILY_REASON, | ||
| "db sign": ORM_FAMILY_REASON, | ||
| "db update": ORM_FAMILY_REASON, | ||
| "db verify": ORM_FAMILY_REASON, | ||
| "migration check": ORM_FAMILY_REASON, | ||
| "migration graph": ORM_FAMILY_REASON, | ||
| "migration list": ORM_FAMILY_REASON, | ||
| "migration log": ORM_FAMILY_REASON, | ||
| "migration new": ORM_FAMILY_REASON, | ||
| "migration plan": ORM_FAMILY_REASON, | ||
| "migration show": ORM_FAMILY_REASON, | ||
| "migration status": ORM_FAMILY_REASON, | ||
| "ref delete": ORM_FAMILY_REASON, | ||
| "ref list": ORM_FAMILY_REASON, | ||
| "ref set": ORM_FAMILY_REASON, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline packages/cli/tests/e2e-coverage.test.ts --items all
rg -n -C 4 'EXCLUSIONS|format|init|lsp|migrate|mountedCommands' \
packages/cli/tests/e2e-coverage.test.ts \
packages/cli/src/v8/cli.tsRepository: prisma/prisma-cli
Length of output: 10547
🏁 Script executed:
#!/bin/bash
set -euo pipefail
cat -n packages/cli/tests/e2e-coverage.test.ts | sed -n '25,185p'
printf '\n--- command descriptions in e2e suites ---\n'
rg -n -C 2 'describeCommand\(|["'\''](format|init|lsp|migrate)["'\'']' packages/cli/e2e packages/cli/tests
printf '\n--- ORM mount tests and command definitions ---\n'
rg -n -C 3 'format|init|lsp|migrate|describeCommand' packages/cli/e2e packages/cli/src | head -n 300Repository: prisma/prisma-cli
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
cli = Path("packages/cli/src/v8/cli.ts").read_text()
coverage = Path("packages/cli/tests/e2e-coverage.test.ts").read_text()
marker = "mountedCommands: Readonly<Record<string, AnyCommand>> = {"
start = cli.index(marker)
end = cli.index("\n};", start)
block = cli[start + len(marker):end]
quoted = re.findall(r'^\s*"([^"]+)":', block, re.M)
all_keys = re.findall(r'^\s*(?:"([^"]+)"|([A-Za-z_$][\w$]*))\s*:', block, re.M)
all_keys = [a or b for a, b in all_keys]
print("quoted-key parser:", quoted)
print("all direct keys:", all_keys)
print("root commands omitted by coverage parser:",
[key for key in ("format", "init", "lsp", "migrate") if key not in quoted])
e2e = set(re.findall(r'describeCommand\(\s*"([^"]+)"', "\n".join(
p.read_text() for p in Path("packages/cli/e2e").glob("*.e2e.ts")
)))
print("root commands with describeCommand:", sorted(e2e & {"format", "init", "lsp", "migrate"}))
print("root commands in EXCLUSIONS:",
sorted(set(re.findall(r'^\s*"([^"]+)":', coverage, re.M))
& {"format", "init", "lsp", "migrate"}))
PYRepository: prisma/prisma-cli
Length of output: 3475
Include unquoted keys in the coverage scan.
mountedCommands() only matches quoted keys, so it omits format, init, lsp, migrate, and feedback. Update the parser, then add e2e coverage or justified EXCLUSIONS entries for the omitted ORM commands.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/tests/e2e-coverage.test.ts` around lines 33 - 51, Update
mountedCommands() to recognize both quoted and unquoted object keys so format,
init, lsp, migrate, and feedback are included in the coverage scan. Then add e2e
coverage for any newly detected ORM commands or add justified entries to
EXCLUSIONS for those that remain intentionally excluded.
The grounding example
The first real
nextpublish (run 31618278670) shipped both packages to npm and then failed its final step:v8.0.0-rc.1exists, marked pre-release — with no tarballs attached, and none can ever be attached now.The decision
This repo's releases are immutable: publishing freezes assets and tag. So the Release step now creates the Release as a draft with the smoked tarballs already attached, then publishes it through the API by the draft's id — assets first, publish second. A draft's tag does not exist yet, so
gh release edit <tag>cannot address it; the id lookup handles that. A rerun that finds the Release already published logs that there is nothing to repair and succeeds, instead of failing a run whose npm publish completed.v8.0.0-rc.1itself stays assetless — immutability cuts both ways. Its tarballs remain retrievable as the run's workflow artifacts and from npm itself. The next release gets its assets.Alternatives considered
gh release createwithout--draft— what the code did; gh creates the release published and uploads after, which immutability rejects.🤖 Generated with Claude Code