Skip to content

fix(doctor): name the recovery each pending adoption state accepts for a pinned literal override - #467

Merged
roodboi merged 1 commit into
nextfrom
wf/g80-adoption-doctor-followups
Oct 24, 2026
Merged

roodboi merged 1 commit into
nextfrom
wf/g80-adoption-doctor-followups

Conversation

@roodboi

@roodboi roodboi commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Follow-ups to #448 (HACK-1391). Refs HACK-1414

Root cause

After #448, hack doctor (and the hack doctor --fix "Kept ..." note) told the user to run hack config adopt --recover --rollback for every pending adoption. That step only works for one kind of pending state:

  • Pending switch (publication.phase is switching or rolling-back): --recover --rollback works.
  • Interrupted preparation stop (the HACK-1318 fixture stopped right after withPreparationStop(... run: async () => 7), so pendingOperation is set and publication is null): no switch exists yet, so repairPublication refuses --recover --rollback, and plain --rollback refuses as busy. Only hack config adopt --recover --stop moves this state on. That retry then refuses to publish over the literal override and journals the switch that --recover --rollback releases.
  • Interrupted retained command of an active adoption (pendingOperation set over an active publication, which is what the doctor --fix pending fixture uses): --recover --rollback refuses there too (the publication is active, not pending). Only hack down --recover settles it. After that, plain --rollback works.

The selector only saw inspectLegacyComposeAdoptionSelection's coarse pending, so it could not tell these three states apart.

Item (3): a repeated --recover --stop after the first literal-env refusal gets E_LEGACY_ADOPTION_BUSY ("busy or requires explicit interrupted-lock recovery"). That interrupted-lock recovery is plain hack config adopt --recover, which completes the switch, and completing it refuses the bound literal override. So following the suggestion led to a second refusal.

Fix

  • src/lib/native-compose-adoption-marker.ts: new inspectLegacyComposeAdoptionSelectionDetail returns the status plus pending: "switch" | "preparation-stop" | "retained-operation" | null. It uses the same reads and refusals as before, and inspectLegacyComposeAdoptionSelection now wraps it with the same result.
  • src/lib/compose-env-at-rest.ts: ComposeEnvAtRestReport.adoptionPending is now false | LegacyComposeAdoptionPending. adoptedComposeEnvOverrideNextStep names the first command each kind accepts. There are two new fixed steps: INTERRUPTED_PREPARATION_... (hack config adopt --recover --stop, then --recover --rollback) and INTERRUPTED_OPERATION_... (hack down --recover, then hack config adopt --rollback). The pending-switch and settled wording are unchanged. Unreadable state is still treated as bound and not pending.
  • src/commands/config-adopt.ts: when hack config adopt refuses with E_LEGACY_ADOPTION_BUSY over a pending switch, the message now adds the step that state accepts. That step is --recover --rollback when the bound override holds literal values, and otherwise --recover to complete or --recover --rollback to restore. The code (E_CONFIG_INVALID), the store message, and every refusal are unchanged. The added text is fixed and contains no values.
  • docs/reference/native-compose-adoption.md: documents the per-state doctor step and the busy next step.
  • The adoption store's state machine is unchanged.

Tests

  • tests/native-compose-adoption-generation.test.ts (HACK-1318 CLI test), in the interrupted-preparation-stop state, before any recovery:
    • --recover --rollback --json and --rollback --json both exit non-zero and leave the receipt byte-identical.
    • inspectComposeEnvAtRest reports adoptionPending: "preparation-stop".
    • checkComposeEnvOverrideAtRest names hack config adopt --recover --stop first, not the pending-switch step.
    • After the refusals, the repeated --recover --stop message is the BUSY message plus PENDING_LITERAL_SWITCH_BUSY_NEXT_STEP, and it does not suggest plain --recover. The test's existing tail still shows plain --recover refusing and --recover --rollback releasing.
  • tests/doctor-fix-adopted-project.test.ts: new pending (retained-operation) fixture with a literal override. It asserts the exact Kept .hack/.internal/compose.env.override.yml: ... <INTERRUPTED_OPERATION step> note, no settled step, no --recover --rollback, no canary, and the file and receipt unchanged.
  • tests/compose-env-at-rest.test.ts: the selector and description for the preparation-stop and retained-operation kinds; the switch test is updated to "switch".

Mutation proofs (each fails the named test; the tree was restored after each):

Mutation Fails
M1: selector returns the pending-switch step for preparation-stop (the behavior before this fix) HACK-1318 CLI test (the doctor message is the old --recover --rollback step) and the HACK-1414 unit test
M2: doctor.ts note uses adoptedComposeEnvOverrideNextStep({ adoptionPending: false }) new doctor --fix HACK-1414 test
M3: adoptionRefusalMessage always returns the bare store message HACK-1318 CLI test (repeated --recover --stop message)
M4: busy step ignores the literal override (always the generic step) HACK-1318 CLI test
M5: marker classifies a pending operation over an active publication as preparation-stop new doctor --fix HACK-1414 test

Evidence

  • Bun 1.4.2 (mise x bun@1.4.2), bun install --frozen-lockfile, bun.lock unchanged.
  • Local run on this Mac with a private HOME, HACK_HOME and DOCKER_CONFIG, HACK_SECRETS_DISABLE_KEYCHAIN_FALLBACK=1, a DOCKER_HOST pointing at a non-existent socket, and stub engines only:
    • bun run --cwd packages/cli typecheck: 0 errors. Root bun run typecheck: 2/2.
    • Root bun run check (includes privacy:check): ok, 2/2 tasks.
    • bun test tests/compose-env-at-rest.test.ts tests/doctor-fix-adopted-project.test.ts: 22 pass, 0 fail.
    • bun test tests/native-compose-adoption-generation.test.ts -t HACK-1318 --timeout 120000 (serial): pass. One earlier run under a load average of about 200 failed one existing exit-code assertion and passed on rerun (HACK-1367 load sensitivity).
    • gitleaks 8.30.1 (gitleaks dir over a git log -p origin/next..HEAD export): no leaks found.
  • Full suite: hosted CI test job at this head (pending at the time of writing).
  • Not needed: the CLI options and consumer agent guidance did not change.

Gaps

  • hack down --recover as the retained-operation step comes from the store contract and the existing retained recovery tests. This PR does not run hack down --recover end to end in the doctor fixture.
  • Under heavy host load, the existing 15 s deadline in leaveRetainedSourceBindOperationPending (tests/helpers/retained-source-bind-adoption.ts) can trip E_LEGACY_ADOPTION_DEADLINE. The new --fix test uses that helper, and so does the existing HACK-1311 doctor test, which failed once locally that way. The helper was not changed here.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

…r a pinned literal override

An interrupted preparation stop (pending operation, no publication) and an
interrupted retained command of an active adoption both reported the pending
switch step 'hack config adopt --recover --rollback', which each of those
states refuses. Doctor and the --fix note now name 'hack config adopt
--recover --stop' and 'hack down --recover' there. A busy refusal over a
pending switch now names the step that state accepts instead of only the
generic interrupted-lock recovery, which completes the switch and refuses a
literal override.

Refs HACK-1414

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Oct 24, 2026

Copy link
Copy Markdown

HACK-1414

@roodboi
roodboi marked this pull request as ready for review October 24, 2026 05:49
@roodboi
roodboi merged commit d3585a2 into next Oct 24, 2026
16 checks passed
@roodboi
roodboi deleted the wf/g80-adoption-doctor-followups branch October 24, 2026 05:49
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.

1 participant