Skip to content

fix(release): keep release PR qualification stable - #19

Merged
myagentdojo merged 2 commits into
mainfrom
codex/fix-release-pr-ci
Aug 7, 2026
Merged

myagentdojo merged 2 commits into
mainfrom
codex/fix-release-pr-ci

Conversation

@myagentdojo

@myagentdojo myagentdojo commented Aug 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

Release Please PRs now stay qualified when the configured automation account changes, while historical releases remain bound to the identity recorded at admission.

The release tests now build explicit version fixtures instead of inheriting the repository's current version. This keeps the suite valid after 0.1.0 and clears the path to publish 0.1.1.

Related: #17

Validation

  • Full bun run prove:all passed.
  • Release-validation suite passed: 40 tests, 0 failures.
  • Fallow changed-code audit found 0 introduced issues.
  • PR 18's Release impact gate passed after correcting the repository automation identity.

Summary by CodeRabbit

  • Bug Fixes
    • Improved release validation to consistently preserve and verify the automation identity associated with a release.
    • Added authentication checks for the configured release automation token before maintenance tasks run.
    • Improved synchronization of version information across release metadata and related files.
    • Strengthened release repair and publication checks, including clearer handling of initial released versions.

@myagentdojo
myagentdojo had a problem deploying to hosted-canary-qualification August 7, 2026 03:10 — with GitHub Actions Failure
@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@myagentdojo, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: df13c106-8388-4fad-9cfa-3943e77fe8fd

📥 Commits

Reviewing files that changed from the base of the PR and between b46da90 and f434c8d.

📒 Files selected for processing (6)
  • .github/workflows/hosted-canary.yml
  • README.md
  • docs/adr/0003-reviewed-versioned-releases.md
  • scripts/repository-readiness.ts
  • scripts/ship-canary.test.ts
  • scripts/ship-canary.ts
📝 Walkthrough

Walkthrough

The release workflow now reuses the automation identity admitted in candidate metadata for repair and publication validation. The maintenance job verifies the Release Please token identity. Test helpers synchronize all release version surfaces.

Changes

Release provenance identity validation

Layer / File(s) Summary
Synchronized release metadata fixtures
.scripts/release-validate.test.ts
Test helpers synchronize package, plugin, marketplace, runtime, and hook versions. Released metadata and repair fixtures use explicit synchronized versions.
Persisted identity checks and maintenance preflight
.github/workflows/release.yml, scripts/release-validate.test.ts
Repair and publication validation compare the pull request identity with the identity persisted in candidate metadata. The maintenance job verifies the Release Please token through gh api user. Workflow tests assert both behaviors.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: keeping release pull request qualification stable during automation identity changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-release-pr-ci

Comment @coderabbitai help to get the list of available commands.

@myagentdojo
myagentdojo had a problem deploying to hosted-canary-qualification August 7, 2026 03:19 — with GitHub Actions Failure
@myagentdojo
myagentdojo temporarily deployed to hosted-canary-qualification August 7, 2026 03:20 — with GitHub Actions Inactive
@myagentdojo
myagentdojo merged commit 7ecc8f4 into main Aug 7, 2026
19 of 21 checks passed
myagentdojo added a commit that referenced this pull request Aug 20, 2026
* fix(dev): detect a superseded development cache version (#18)

* fix(dev): detect a superseded development cache version

A version bump leaves the previous cache directory behind. Claude Code
stops listing it and writes `.orphaned_at` into it, so it becomes files
nothing reads inside the profile this lifecycle owns.

Existing orphan detection could not see this. It runs only when Claude
lists no installation at all, and it defines an orphan by a dangling
link. A superseded directory dangles nothing: it points at the same live
payload as the installed one, so every link resolves. Registration is the
discriminator, not link health, so detection now compares the version
directories on disk against the install path Claude reports.

`check` and `install` refuse while one stands, because reporting a
profile clean while it holds files nothing reads is the silent result
this detection exists to end. `restore` and `watch` stay reachable, since
a superseded directory is inert until the next install reuses the name.

* refactor(dev): drop the unused orphan marker constant

ORPHAN_MARKER_FILE was declared and never referenced. Detection reads
registration, not the marker, so the constant asserted a dependency the
code does not have.

Records why the marker is not the test: Claude Code writes `.orphaned_at`
and later collects the directory on its own schedule, so gating on it
would bind this lifecycle to an undocumented internal file and miss any
directory left behind without one.

---------

Co-authored-by: Nathan Vale <hi@nathanvale.com>

* feat(dev): report whether the payload came from the last successful build (#14)

* feat(dev): report whether the payload came from the last successful build

A failed build was silent. Its message scrolled past in a terminal the
workflow gives nobody reason to watch, `/reload-plugins` loaded the previous
build, and the session served code that was not on screen.

`build` now writes a receipt after every attempt, including a failed one, and
`check` verifies it against the payload digest rather than believing it.

The receipt lives at `.dev/claude/build-receipt.json`, outside the payload.
Inside `plugin/` it would have no counterpart symlink in the installed cache,
so `verifyLinkedPayload` would report the installation as no longer canonical;
`validateBunOnlyPayload` separately rejects unrecognized payload surfaces, and
the release archive is compared byte for byte across two builds.

Freshness is read before `prepare` rebuilds. Evaluating it afterwards compared
the payload against the receipt that same build had just written, so every
answer was `fresh` and a payload edited since the last reload reported ready.

`generate:check` moves inside `build.ts` rather than running as a separate
`&&` stage, where it failed before any receipt was written and left the
previous `succeeded` receipt to be read as proof the payload was current.

* test(dev): assert receipt identity against a fixture, not the ambient build

The test read the repository's own receipt, so it passed on a machine that had
built and failed in CI, where `.dev/` is gitignored and never present. It
proved the local machine's history rather than the writer's behaviour.

It now writes a receipt into a controlled checkout and asserts on that. A
second test covers the payload copied outside git: the git fields are omitted
rather than faked, and the digest that proves freshness is still recorded.

Verified by moving the local receipt aside and running the full suite in CI's
condition: 745 pass, 0 fail.

* fix(dev): keep one freshness state across install re-inspection

Applies four review findings.

The re-enable path reported two freshness states at once. `install`
re-inspects the profile after enabling the plugin, and that second
inspection reads the profile rather than the receipt, so `result` fell
through and re-evaluated freshness after the build had already rewritten
the receipt. The status read `fresh` beside a `nextAction` describing the
state before the build. That is the masking this feature exists to end,
reappearing in its own install path. The captured snapshot now carries
forward.

`writeBuildReceipt` read plugin.config.json inside its own write guard,
so an invalid configuration returned before any receipt was written. The
failure most likely to break a build therefore left `check` reporting
`unproven` instead of `build-failed`. The version now falls back to
`unknown`, because the field is required and a reader rejects a receipt
without it.

`readBuildReceipt` accepted any numeric schemaVersion, so a receipt from
an incompatible schema could pass digest verification and report `fresh`.

The unwritable-location test never reached the write: its checkout had no
configuration, so it returned at the read above. It now uses a valid
configuration and a locked directory.

Each fix carries coverage that fails without it; the re-enable test was
confirmed to reproduce `fresh` against `bundler-failure` before the fix
was restored.

---------

Co-authored-by: Nathan Vale <hi@nathanvale.com>

* fix(dev): name the production installation the install preview replaces (#19)

* fix(dev): name the production installation the install preview replaces

The preview is the only disclosure before a profile write, and `--apply`
uninstalls the production Plugin Installation and removes its Marketplace
before installing the development one. The message read "Review the
captured production state, then rerun with `--apply`" whether or not
production was present, so it asked for consent to an action it never
named. `sideEffects` could not carry this: it records mutations that have
already happened, and a planned one there would misreport the profile as
changed.

The preview now names the plugin id, the `--keep-data` flag that makes
the replacement recoverable, the Marketplace removal, and `restore` as
the exit. With no production installation present it claims no
replacement.

* fix(dev): correct the preview recovery command and prove every branch

Applies review findings across three axes.

The preview named `bun run dev -- claude restore` as the command that
puts production back. Bare `restore` previews like every operation here,
so an agent following that sentence runs a no-op and reports the profile
restored. It now names `restore --apply`. This was a fail-closed defect
inside the string added to make the operation honest.

The new doc comment was inserted between `readyNextAction`'s comment and
its function, stripping documentation from a function this change does
not own. The preview helper now sits after it with its own comment.

`${pluginName}@${pluginName}` was reassembled at five sites. They read
`productionPluginId` now, so the id the glossary names has one owner.

The absent-case test asserted only the absence of "uninstall", which the
replaced string also satisfied, so it pinned nothing. It asserts the
sentence. A Marketplace-only production state is a third reachable branch
that was unproved; it has coverage. All three preview tests were
confirmed to fail against the string they replaced.

The dev-mode skill told an agent to preview and seek approval without
saying the preview may name a production uninstall. It now says to quote
that sentence rather than summarize it.

* refactor(dev): carry the inspected plugin name on the state it describes

The install preview re-read plugin.config.json to name the plugin, and a
comment claimed the name was unavailable any earlier because the
restoration snapshot is captured after the preview returns.

That reason was wrong. Both inspectors already resolve the name to read
the profile at all, and `prepare` returns their result, so the state
handed to the preview was produced by a function that had the name and
discarded it. The snapshot was never the only carrier.

InspectedState now carries `pluginName`, and the preview reads it from
the state it was given. A consumer naming the plugin therefore names the
one the inspection named, instead of re-reading the configuration and
being able to disagree with the state it holds.

* docs(dev): describe the preview replacement as the conditional it is

The doc comment and the dev-mode skill both stated that `--apply`
uninstalls the production Plugin Installation and removes its
Marketplace, as one unconditional pair. The code branches on each
separately, and a Marketplace can stand without its Plugin Installation,
so a reader of either text expected a mutation the profile may never see.

Both now name the conditions. The comment also records that the inverse
case throws PRODUCTION_MARKETPLACE_MISSING before reaching the preview,
so the reachable states are named rather than implied.

---------

Co-authored-by: Nathan Vale <hi@nathanvale.com>

* fix(test): give the dev lifecycle cases an explicit subprocess budget (#20)

`isolated profile snapshots cannot overwrite each other's restore state`
timed out at Bun's 5s default on an isolated run, not only under load.

The cost is the lifecycle, not the assertions. Each `run` here spawns
`dev.ts`, and the lifecycle rebuilds the payload before it inspects
anything, so one invocation measures ~940ms. That case makes four and
runs at 4.2s; the disabled-link repair case runs at 2.5s. Every other
case in the file measures 1.3s or less and is left alone.

The budget is declared once, beside the `run` helper that spends it,
matching how update-hosted.test.ts states its own process timeouts
rather than inheriting one. Raising the ceiling does not make these
faster: the payload rebuild per invocation is the real cost, and
removing it would change the spawn seam.

Co-authored-by: Nathan Vale <hi@nathanvale.com>

---------

Co-authored-by: Nathan Vale <hi@nathanvale.com>

This branch was previously deployed

1 inactive deployment
hosted-canary-qualification — f434c8d8 Deployed Aug 7, 2026 by myagentdojo via Qualify immutable hosted candidates #19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants