Skip to content

fix(doctor): name explicit recovery rollback for a literal override a pending adoption pins - #448

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

roodboi merged 1 commit into
nextfrom
wf/g66-adoption-deadline-followups

Conversation

@roodboi

@roodboi roodboi commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Root cause

Follow-ups to #397 (HACK-1391), all on the retained-adoption deadline guards and the HACK-1318 pending state:

  1. The keep-CHANGED branch of the mutateRetainedContainers catch (an E_LEGACY_ADOPTION_CHANGED raised after the caller deadline keeps its code) had no test: dropping the exclusion failed nothing.
  2. requireMutationDeadline's expiry branch was never the first guard to fire in any test, so making it issue E_LEGACY_ADOPTION_CHANGED passed every focused test.
  3. After a refused hack config adopt --recover --stop (HACK-1318: the switch stays journaled, so the adoption selection is pending), hack doctor's compose-env-at-rest next step told the user to run hack down and plain hack config adopt --rollback, both of which that pending state refuses. The only command that releases the switch is hack config adopt --recover --rollback.
  4. tests/helpers/retained-routing-adoption.ts still said a retained mutation refuses as E_LEGACY_ADOPTION_CHANGED on caller-deadline expiry.
  5. The HACK-1283 test comment attributed the deadline code to HACK-1318 instead of HACK-1340.
  6. A second --recover --stop after the refusal does not reach the publication boundary again, so it gets a generic refusal without the literal-env message. This was undocumented.

Fix

  • src/lib/compose-env-at-rest.ts: ComposeEnvAtRestReport gains adoptionPending (from a new composeEnvOverrideAdoptionBinding, which keeps composeEnvOverrideAdoptionBound as a wrapper). adoptedComposeEnvOverrideNextStep(report) picks PENDING_ADOPTED_COMPOSE_ENV_OVERRIDE_NEXT_STEP ("release the pending adoption switch with 'hack config adopt --recover --rollback' ...") for a pending adoption and the unchanged settled wording otherwise. Unreadable adoption state stays bound and not pending, so its wording is unchanged.
  • src/commands/doctor.ts: the --fix "Kept ..." note uses the same selector.
  • docs/reference/native-compose-adoption.md: documents the repeated --recover --stop refusal and the doctor next step for the pending switch.
  • Comment fixes in the routing helper (DEADLINE on expiry) and the HACK-1283 test (HACK-1340).
  • No adoption-store behavior changed; every existing refusal is kept. The repeated --recover --stop is documented, not reworded: the product issues E_LEGACY_ADOPTION_BUSY there (from requireStablePublication, which runs before the publication selection), not the STATE code the unit text guessed.

Tests

  • tests/native-compose-adoption-routing-generation.test.ts
    • "HACK-1391: a binding drift reported after the caller deadline keeps its binding-change code": volume-birth drift after the stop effect, with the synthetic clock jumping past the 15 s caller deadline right after the first read of the drifted volume. Expects E_LEGACY_ADOPTION_CHANGED with deadlineExpired: true, bindingUnchanged: false.
      Mutation: replacing the catch condition with plain Date.now() >= deadline fails only this test (received E_LEGACY_ADOPTION_DEADLINE); HACK-1283 and HACK-1340 tests still pass under it.
    • "HACK-1391: the callback revocation fence past the caller deadline refuses as a deadline, not a binding change": a direct withPreparationStop whose run jumps the clock and then calls input.assertActive(); no probe or check() runs in between, so only requireMutationDeadline can refuse. Expects E_LEGACY_ADOPTION_DEADLINE.
      Mutation: making the expiry branch refuse("E_LEGACY_ADOPTION_CHANGED") fails only this test.
  • tests/compose-env-at-rest.test.ts: new unit test for the pending wording (plaintext and unverifiable overrides), and the unreadable-state test now asserts adoptionPending: false and no --recover --rollback.
    Mutation: a selector that ignores adoptionPending fails this test.
  • tests/native-compose-adoption-generation.test.ts (HACK-1318 CLI test): after the two refused recoveries it now asserts (a) a repeated --recover --stop --json returns E_CONFIG_INVALID with the E_LEGACY_ADOPTION_BUSY message and no publication diagnostic, with the switch still journaled; (b) inspectComposeEnvAtRest reports adoptionBound: true, adoptionPending: true; (c) doctor's checkComposeEnvOverrideAtRest names hack config adopt --recover --rollback and neither plain rollback nor hack down.
    Mutation: an inspector that never reports pending fails this test (received adoptionPending: false).

Evidence

  • Bun 1.4.2 (mise x bun@1.4.2), bun install --frozen-lockfile, bun.lock unchanged.
  • Local (this Mac, private HOME/HACK_HOME/DOCKER_CONFIG, HACK_SECRETS_DISABLE_KEYCHAIN_FALLBACK=1, stub engines only):
    • bun run --cwd packages/cli typecheck: 0 errors.
    • root bun run check (includes privacy:check): 2/2 tasks successful, 0 errors (34 pre-existing complexity warnings in unrelated files).
    • bunx biome check on the six changed files: 0 errors, 1 pre-existing warning (the helper's probe complexity).
    • bun test tests/native-compose-adoption-routing-generation.test.ts --timeout 60000 (serial, whole file): 34 pass, 0 fail (129 s).
    • bun test tests/compose-env-at-rest.test.ts: 17 pass.
    • bun test tests/native-compose-adoption-generation.test.ts -t "HACK-1318|HACK-1273" --timeout 120000: 4 pass (32 s).
    • bun test tests/doctor-adopted-project.test.ts tests/doctor-fix-adopted-project.test.ts tests/compose-env-at-rest-cli.test.ts: 17 pass.
    • gitleaks 8.30.1 (gitleaks dir over a git log -p origin/next..HEAD export): no leaks found.
  • M3 full suite (bun run test at e650edc in a detached m3-ci/G66 worktree, removed afterwards): PENDING. The run is queued behind 20+ units on the shared m3-ci lock (each full suite takes ~45 min). It runs detached on the M3 as ~/.hack-v5-private/m3-ci/G66-run.sh, verifies the head is e650edc, logs to ~/.hack-v5-private/m3-ci/G66-full.log (tail) and G66-test.log (full), and removes its worktree and the lock on exit. This line is updated when it reports; until then the full-suite gate is open.
  • Not needed: no CLI surface, consumer agent guidance, package.json scripts or pinned toolchain files changed.

Gaps

  • The HACK-1318 pending state covers a journaled switch. An interrupted retained operation on an active adoption (pendingOperation set) is also reported as pending by the selection inspector, and the new wording names --recover --rollback for it too; repairPublication refuses that sub-state (it requires a switching/rolling-back publication), so that user still needs the adopted command's own recovery first. The previous wording was wrong for that sub-state as well (plain --rollback refuses while an operation is pending); distinguishing the two would need the receipt's operation state in the doctor report.
  • The drift-after-deadline test relies on the store rereading the drifted volume before comparing (the bracketed scan); the assertion is driftedReads >= 1, so a scan-shape change does not break it, but the clock jump must still land before the comparison for the test to prove the catch branch. Its deadlineExpired: true assertion guards that.
  • The second new test exercises requireMutationDeadline through the callback fence only; the other call sites share the same function.

Refs HACK-1391

🤖 Generated with Claude Code

… pending adoption pins

After a refused `hack config adopt --recover --stop` (HACK-1318, journaled
switch), `hack doctor` told the user to run `hack down` and plain
`hack config adopt --rollback`, both of which that pending state refuses.
The compose-env-at-rest report now carries `adoptionPending`, and the
check and `--fix` note name `hack config adopt --recover --rollback` while
the adoption is pending; the settled wording is unchanged.

Deadline-guard follow-ups from #397 (HACK-1391):
- prove the keep-CHANGED branch of the `mutateRetainedContainers` catch
  with a drift reported after the caller deadline (mutation: dropping the
  exclusion fails only this test)
- prove `requireMutationDeadline`'s expiry branch directly through the
  callback revocation fence (mutation: issuing CHANGED fails only this test)
- correct the retained-routing helper comment (deadline expiry is
  E_LEGACY_ADOPTION_DEADLINE) and the HACK-1283 comment (HACK-1340)
- document that a second `--recover --stop` after the refusal gets the
  generic E_LEGACY_ADOPTION_BUSY refusal, and assert it in the HACK-1318 test

Refs HACK-1391

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Oct 23, 2026

Copy link
Copy Markdown

HACK-1391

@roodboi
roodboi marked this pull request as ready for review October 24, 2026 02:43
@roodboi
roodboi merged commit 8527add into next Oct 24, 2026
17 checks passed
@roodboi
roodboi deleted the wf/g66-adoption-deadline-followups branch October 24, 2026 02:43
roodboi added a commit that referenced this pull request Oct 24, 2026
Resolve the src/commands/doctor.ts docblock conflict with #448 to the
PR side: keep the extended checkProjectEnvKey docblock and its export.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roodboi added a commit that referenced this pull request Oct 24, 2026
Follow-ups to #433 (HACK-1375) and #435 (HACK-1376), both already on `next`.

## Root cause

1. **The unset compare-and-swap was not tested.** `unsetProjectEnvValue` passes
`expectedExisting: text` to the layer write. If you delete that line, every lock, hydrate and
config test still passes, because only the set path had a paused-writer conflict test. 2. **Some
layer locks can never be reclaimed, and the message did not say so.** `withEnvLayerLock` reuses
the projects-registry owner protocol. That protocol never removes a malformed receipt, a receipt
it does not trust (another uid, or writable by group or others), a receipt whose PID now belongs
to a live process, or a dead owner behind a leftover `<lock>.recovery` guard. In each of these
cases every `env set/add/unset/hydrate` on the layer waited 10 s and then printed `Another hack
env command is writing <file>; … Retry when it finishes.` No retry could ever succeed, and the
message did not name the lock file. 3. **A key can become unignored after a later `git init`.**
In a non-git directory, the first `--secret` creates the key and writes its root `.gitignore`
entry. Outside git, an existing key deliberately triggers no `.gitignore` write. So if
`.gitignore` is deleted and the directory then gets `git init`, the key is not ignored, and Hack
is not running at that moment. 4. **The re-include refusal named the wrong cause.** The ignore
probe that runs after the entry is in place produced the same "Hack could not add it to
<.gitignore>" message as a failed write. That is wrong when `.gitignore` already contains
`.hack.secret.key` followed by `!.hack.secret.key`.

## Fix

- `src/lib/env-layer-lock.ts`: `EnvLayerLockTimeoutError` now has a `reason` (`held` |
  `recovery_guard`) and the `lockPath`. The message is still one value-free line with no stack:
  - `held`: `Another hack env command is writing <file>; gave up after waiting 10s for its lock
  at <lock>. Nothing was written. Retry when it finishes. If no hack env command is running,
  Hack cannot reclaim that lock itself: remove <lock> and retry.`
  - `recovery_guard`: when the registry reports a dead owner behind a busy guard, the message
  keeps the registry's `recovery guard is busy (interrupted recovery requires offline
  inspection)` detail and names both `<lock>.recovery` and `<lock>` as the files to remove.
  - The lock helper still never removes anything itself. `src/lib/projects-registry-lock.ts` is
  unchanged; the classification reads the registry's existing timeout detail. That file belongs
  to G73 / HACK-1377.
- `src/lib/project-env-config.ts`: `projectEnvKeyUnignoredError` now takes a `cause`.
  `unwritable` keeps the existing message. `reincluded` says the root `.gitignore` lists the key
  but a later rule in that file re-includes it, and asks the user to remove that rule. Once the
  entry is present, no deeper ignore file can apply to a key at the project root, so a later
  rule in that same file is the only possible cause.
- `src/commands/doctor.ts`: exports `checkProjectEnvKey` so the real doctor check can be tested
  end to end. It also moves a misplaced docblock onto that function. The check's behavior is
  unchanged.
- `docs/env.md`: documents unreclaimable locks and the recovery step, the re-include cause, and
  the `git init` decision. It also adds an `env-key-unignored` example to the doctor table.

### Decision on a later `git init` (item 3)

Watching for `git init` is out of scope for env writes, because Hack runs no code when it
happens. Outside git, an existing key still needs no entry, and Hack does not recreate a root
`.gitignore` the user deleted. That behavior is unchanged and covered by the existing test. The
guarantee comes back at the next Hack touchpoint:

- `hack doctor`'s env key check reports `env-key-unignored` (warn) with a remedy. This check
  already existed; the new test proves it catches this exact sequence.
- The first `--secret` set or hydrate in the new repository adds the entry before it uses the
  key, or it refuses (#433 behavior).

## Tests (each proven by mutation)

| Test | Mutation that makes it fail | | --- | --- | | `project-env-layer-lock`: *an unset
refuses with a conflict when a writer that takes no lock changed the layer after its read*. A
`spyOn` pauses `resolvePrimaryLocalProjectDir`, the one awaited call between the local unset's
read and its write. The test edits the layer, resumes, and expects `AtomicWriteConflictError`
with the edit kept. | delete `expectedExisting: text,` from the unset: 1 fail | | `project-env-
layer-lock`: *a layer lock Hack cannot reclaim…* uses three receipts: malformed, group-writable
with an exited PID, and PID 1 standing in for a reused PID. Set and unset each refuse with the
exact message, the receipt is left untouched, and removing the named lock lets the retry
succeed. The existing held-lock test asserts the new message too. | restore the old message: 2
fail | | `project-env-layer-lock`: *a dead owner behind an interrupted recovery guard…* covers a
set (300 ms wait) and a hydrate (default 10 s): both give the `recovery_guard` message with both
paths, write nothing, and leave the lock and guard in place. After both are removed, the set
succeeds. | classify the guard case as `held`: 1 fail | | `project-env-key-ignore`: the re-
include test now expects the `reincluded` message, does not contain "could not add it", and
checks that `.gitignore` was not rewritten. | send `unwritable` for the post-entry probe: 1 fail
| | `project-env-key-ignore`: *a key whose entry was removed before a later git init…* runs the
exact reported sequence through the source CLI, then the real `checkProjectEnvKey`: status
`warn`, `env-key-unignored`, and no key bytes or absolute root in the output. The next
`--secret` set repairs the entry and doctor returns `ok`. | drop `inspectLocalKeyIgnored` from
doctor: 1 fail |

Every fixture uses a private HOME, HACK_HOME, global config and Docker config,
`HACK_SECRETS_DISABLE_KEYCHAIN_FALLBACK=1`, and git without global or system config. The env
layer is written before any `--secret`. The in-process doctor call swaps in the same private
environment and restores it afterwards.

## Evidence

- Manual repro before the fix: (2) dead owner plus guard, and a malformed receipt, gave the old
  "Retry when it finishes." message. (3) after `git init`, `git check-ignore` exits 1 and `git
  status` lists `.hack.secret.key`. (4) gave the "could not add it" message. After the fix, the
  same script prints the messages above, and removing the named lock makes the retry exit 0.
- Focused: `tests/project-env-layer-lock.test.ts` 12/12 and `tests/project-env-key-
  ignore.test.ts` 8/8 pass locally.
- `bun run check` (includes `privacy:check`) exit 0. `bun run --cwd packages/cli typecheck` exit
  0. gitleaks 8.30.1 (`gitleaks dir` on the `git log -p origin/next..HEAD` export): no leaks.
- The CLI surface and consumer agent guidance did not change, so no reference or plugin
  regeneration was needed.
- Full suite: hosted CI `test` at this PR head (see checks).

## Gaps

- In a wider local run of 11 related env/doctor files, 13 tests in `project-env-write-refusals`,
  `project-env-stable-ciphertext` and `project-env-worktree-key` failed with
  `beforeEach/afterEach hook timed out` while the host load average was 51–60 on 18 cores. None
  of them asserts a message this PR changes. Hosted CI is the evidence for them.
- The refusal is still a `ClosedCliError` with no `E_*` code (as before). Adding a lock-busy
  code would change the error-code registry and its generated docs, so it is left for a separate
  decision.
- Classifying `recovery_guard` depends on the registry's existing timeout text. If G73 rewords
  that text, the guard test fails in the merge train rather than silently falling back to
  `held`.
- The `held` message cannot tell a live writer from an unreclaimable lock without parsing the
  receipt (code G73 is editing), so it covers both cases: retry, or remove the lock when no hack
  env command is running.
- The unset CAS test uses `spyOn` on an ESM namespace export (`worktree-local-config.ts`), in
  process and restored in `finally`.

## Round 2: merge with current next

While this PR was being verified, `next` moved from 7d9c89f to 5208ee5 (#404, #446, #430,
#439, #421, #448, #452, #405), and the PR became `CONFLICTING`.

- Merge commit 7ac7cbb merges `origin/next` (5208ee5) into the branch. The only conflict was
  the `checkProjectEnvKey` docblock in `src/commands/doctor.ts`, which #448 also moved. It is
  resolved to this PR's side: the extended docblock plus `export async function
  checkProjectEnvKey`. Compared with next, `doctor.ts` now differs only in those lines.
- No other code changed in this round. The diff against next is still the same 6 files.
- Gates on the merged head: `bun run check` (includes `privacy:check`) exit 0. `bun run --cwd
  packages/cli typecheck` exit 0. Focused tests `project-env-layer-lock`, `project-env-key-
  ignore` and `cli-spawn-env-isolation` pass 27/27 (`--timeout 120000`) under #405's new
  preloads. gitleaks 8.30.1 (`gitleaks dir` on the `git log -p origin/next..HEAD` export): no
  leaks.
- Full suite: hosted CI run 40056707145 at 7ac7cbb (base 5208ee5): `test` passed in 38m45s,
  plus docker-e2e 1-4/4, Candidate core (ubuntu), Config compiler (ubuntu x64/arm,
  macos-15-intel), Runtime state models, linux-process-lifetime, runtime-images and secret-scan.
  The two `macos-latest` jobs were still queued for a runner after about an hour. The earlier
  green run 39978726682 was at ed877f5, before the base moved, so it does not count for this
  head.

Refs HACK-1386

🤖 Generated with [Claude Code](https://claude.com/claude-code)
roodboi added a commit that referenced this pull request Oct 24, 2026
…r a pinned literal override (#467)

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](https://claude.com/claude-code)
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