Repository navigation
Conversation
…drain isolated test output The busy refusal over a journaled rollback (`rolling-back`) named plain `hack config adopt --recover`, which completes only a `switching` publication and refuses there; it now names only `--recover --rollback`. Correct the classify() comment: the receipt parser also admits a pending preparation stop over a rolled-back publication. runtime-core's `run_isolated` test helper read the child's stdout only after the child exited, so a failing child with more than a pipe buffer of output blocked until the 120 s hang guard and reported a watchdog instead of its failure. Drain stdout and stderr on reader threads while waiting. Refs HACK-1427 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h the override moved aside Add the HACK-1427 report's exact state: the HACK-1318 literal fixture after its switch is journaled, rewritten to `rolling-back`, with the bound override moved aside. `--recover --stop`, plain adoption and `--stop` must name only `--recover --rollback`, never plain `--recover`, which refuses there. Refs HACK-1427 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ved aside (HACK-1437) An interrupted adoption rollback whose bound literal .hack/.internal/compose.env.override.yml was moved aside could not be recovered by any `hack config adopt` command. The saved read binds every generated override by bytes and identity, ctime included, so `--recover --rollback` journaled `rolling-back` and then refused, plain `--recover` refuses a rollback, moving the file back changes its ctime, and the busy message named `--recover --rollback` again. Rollback never reads, runs, rewrites or removes the generated env override. It restores the held legacy originals, archives the candidate and verifies the original resources. Rollback (from `active`, `switching` and `rolling-back`) now reads the saved proof with the env override released: the file may be missing, moved back or replaced. The saved config_files path list keeps its path, and every other check stays exact (managed revision, runtime override, .hack/.internal identity, config hashes, resources, stopped state, held originals, candidate). Execution and switch completion keep the full binding. Every pending-switch step now names only a command that the state does not refuse for a known reason. A `switching` publication whose bound override was moved or changed gets PENDING_CHANGED_SWITCH_BUSY_NEXT_STEP, which names only `--recover --rollback`. A refused plain `--recover` adds the step when the cause is known (`rolling-back`, or a moved or changed override). The rationale for releasing the proof is written in docs/reference/native-compose-adoption.md. Refs HACK-1437 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two follow-ups from the #399 and #467 verifiers (HACK-1427).
1.
run_isolatedno longer hides a noisy child's failure behind the hang guardpackages/runtime-core/src/provider/test_executable.rs:run_isolated(from #387, extended by #399) used to pipe the child's stdout and read it only after the child exited. A failing child that wrote more than a 64 KiB pipe buffer blocked on its write until the 120 sISOLATED_HANG_GUARD. The guard then reported a watchdog panic instead of the real failure.test <name> ... ok/1 passedlines, as before.provider/graph/startup/runtime/rebind/tests.rs:219, only delegates to this shared helper, so this change fixes it too. No otherrun_isolatedexists.New test
isolated_child_failure_with_large_output_is_reportedlaunches the ignored childnoisy_failure_child. That child writes 160 KiB to each stream and then panics. The test requires the parent to fail well within the guard with the child's own failure, not "watchdog". The message must hold the panic text and the stderr end marker, and must be bounded.2. The busy refusal over an interrupted rollback names the command that works there
src/commands/config-adopt.ts: once a switch's rollback is journaled (publication.phase: rolling-back), plainhack config adopt --recoverrefuses whatever the override holds, becausecompletePublicationaccepts onlyswitching. The genericE_LEGACY_ADOPTION_BUSYstep still named it first whenever the override was not literal. The busy step forrolling-backis nowPENDING_ROLLBACK_BUSY_NEXT_STEP, which names onlyhack config adopt --recover --rollback. Theswitchingwording (HACK-1414) is unchanged. The text is fixed and value-free.docs/reference/native-compose-adoption.mdis updated.src/lib/native-compose-adoption-marker.ts: theclassify()comment now matchespendingSelectionMatches: a pending stop is also admitted over arolled-backpublication.LegacyComposeAdoptionSelectionDetailgainsswitchPhaseso the CLI can tell the two pending-switch phases apart.Validation
All at head
2adbd9dc:bun run check:localpasses (fmt and clippy-D warnings). The focusedprovider::test_executabletest passes.bun run test:localwas run twice on a heavily loaded local host (load average 50 to 130). Each run had 1051 passing tests and one different failure:dependency_slots::concurrent_claim_writers_serialize_without_overlapping_slots(its 3 s lock deadline) in the first run, andrelay_owner::publication::dead_publication_recovery_requires_unchanged_receipt_and_socketin the second. Neither test touchesrun_isolatedor any changed file, and both modules pass when rerun alone (dependency_slots20/20,relay_owner::publication11/11 three times). Hosted Candidate core on macOS and ubuntu is green.--timeout 60000(2 pass, 146 s), because these tests are load-sensitive (HACK-1367).origin/next..HEAD: no leaks.bun run check(includesprivacy:check): exit 0.bun run --cwd packages/cli typecheck: 0 errors.2adbd9dc: all checks green, including Candidate core (macos-latest, ubuntu-latest),testshards 1-4/4, docker-e2e shards 1-4/4, Config compiler (4 targets), Runtime state models, linux-process-lifetime, runtime-images and secret-scan.Refs HACK-1427
Round 2 (rework after verifier: no PR existed)
The first implementation round committed
ad916597locally but never pushed it, so the verifier found no branch and no PR. This round reviews that commit, adds the report's exact repro, reruns every gate, pushes the branch and opens this PR.Added test.
CLI busy refusal over an interrupted rollback of a literal-bound switch does not name plain recovery once the override is moved aside (HACK-1427)sets up the exact reported state. It starts from the HACK-1318 literal fixture, journals the switch with a first--recover --stop, rewrites the receipt phase torolling-back, and moves the bound override aside.--recover --stop --json, plain--jsonand--stop --jsonmust each return the store's BUSY message plusPENDING_ROLLBACK_BUSY_NEXT_STEP, and must never name plain--recover. Plain--recovermust refuse and leave the receipt unchanged.The existing round-1 test,
...names only the recovery rollback that finishes it (HACK-1427), covers an active adoption with a reference-form override, rewritten torolling-back. It shows that the named--recover --rollbackactually completes the rollback and restores the originals. Plain--recoverrefuses there.Mutation proof.
rolling-backbranch ofpendingSwitchBusyNextStepdisabled, both HACK-1427 adoption tests fail (the busy message is the generic step). Restored, both pass.isolated_child_failure_with_large_output_is_reportedfails after 120 s withexceeded its watchdog. Restored, it passes.Known limit (reported separately). In the reported state, the bound override has been moved aside.
--recover --rollbackthen refuses asE_LEGACY_ADOPTION_STATE, because the saved generated-input proof pins the file, including its ctime. Moving the file back does not satisfy the proof, because a rename changes ctime. The message now names the only command that can finish arolling-backpublication, and that command works whenever the bound inputs are intact. With the bound file moved away, though, nohack config adoptcommand finishes the rollback. That is outside this unit and is reported as a follow-up finding. (Resolved in round 3 below, HACK-1437.)Round 3 (HACK-1437: a supported way out of an interrupted rollback with a moved override)
The round-2 verifier found that the known limit above was a real dead end. In the HACK-1318 literal fixture, journal the switch with
--recover --stop, then move.hack/.internal/compose.env.override.ymlaside.--recover --rollbackjournaledrolling-backand then refused withE_CONFIG_INVALID. Plain--recoverrefuses a rollback. The busy message named--recover --rollbackagain, and moving the file back did not help, because a rename changes ctime. This round fixes that path. It mergesorigin/nexttwice, first1281184band then7dac4a03after next moved again. Both merged cleanly and neither touches the files this round changes. The fix commit is0e17a563and the head is80c9b1fe.What the proof protects. A saved read binds each generated override by its bytes and file identity, ctime included. This protects adopted execution and switch completion, which must run exactly the inputs that preparation qualified and whose Compose config hash the retained containers carry. The binding also keeps
hack doctor --fixand the env scrubs from removing a pinned file. Rollback restores the held legacy originals, archives the candidate and verifies the original resources. It never reads, runs, rewrites or removes the env override, which is derived from the managed env layers and rewritten by the nexthack up. No data depends on it.Change.
src/lib/native-compose-adoption-projection.ts:readSavedLegacyComposeAdoptionProjectiontakesenvOverride: "bound" | "released", withboundas the default. Inreleasedmode the env override is neither read nor compared. Every other generated proof stays exact: the.hack/.internaldirectory identity and the runtime override's bytes and identity. The savedconfig_filespath list keeps the override's path, so the containers are still matched against the label Compose recorded. The new value-freesavedLegacyComposeEnvOverrideChangedreports a moved or changed override when the other generated inputs still match.src/lib/native-compose-adoption-generation.ts: rollback reads inreleasedmode inrollback()(fromactive) and incompleteRollback(--recover --rollbackfromswitchingorrolling-back). Execution, leases, publication and completion still usebound. The new read-only store methodpendingSwitchEnvOverrideChanged()backs the CLI hint.src/commands/config-adopt.ts: every pending-switch step names only a command that the state does not refuse for a known reason. The newPENDING_CHANGED_SWITCH_BUSY_NEXT_STEPcovers aswitchingpublication whose bound override was moved or changed, and names only--recover --rollback. A refused plain--recovernow adds the step when its cause is known (rolling-back, or a moved or changed override). The text is fixed and value-free.docs/reference/native-compose-adoption.md: a new "Rollback releases the generated env override (HACK-1437)" section gives the written rationale. It covers what the binding protects, why rollback can release it, what rollback still checks, and the step for each pending state. The version 3 paragraph notes the exception.Tests (
tests/native-compose-adoption-generation.test.ts; long tests use the named budgetROLLBACK_RECOVERY_BUDGET_MS, HACK-1367):CLI recovery rollback finishes a pending switch whose bound literal override was moved aside and back, without hand edits (HACK-1437). With the override moved aside, BUSY names only--recover --rollback(PENDING_CHANGED_SWITCH_BUSY_NEXT_STEP). Plain--recoverrefuses, journals nothing and names the same step. After the file is moved back (same inode, new ctime), BUSY names the literal step. The test then parses the message and runs the one command it names. That command must finish: the originals are restored, the selection isrolled-back, and the literal file is untouched and no longer pinned.rolling-backwith the override moved aside: the round-2 test (renamed..., and that rollback finishes (HACK-1427, HACK-1437)) now also checks that plain--recovernamesPENDING_ROLLBACK_BUSY_NEXT_STEP, then runs the named command to completion. The override stays absent and the moved file is untouched.CLI rollback of an active adoption releases a replaced generated env override while plain recovery stays refused (HACK-1437):--rollbackfinishes with a different file at the bound path and does not rewrite it.rollback releases a moved-aside generated env override that execution still refuseschecks thatloadActivestill refuses while rollback succeeds.rollback still binds the generated runtime override when the env override is releasedrewrites the runtime override with identical bytes (a new ctime) and checks that rollback still refuses before anything moves.Mutation proof (each reverted afterwards):
releaseddisabled): store-level release, state 1, state 2 and active-rollback tests fail at the rollback step. The strictness test still passes.--recover).--recoverdisabled: state 1 and state 2 fail at the completion message.config_files: the store-level release, state 1 and active-rollback tests fail (rollback refuses on the binding).Gates (fix commit
0e17a563; rerun at head80c9b1feafter the second merge):gitleaks dirover agit log -p origin/next..HEADexport): no leaks.bun run check(includesprivacy:check): exit 0, 34 existing complexity warnings.bun run --cwd packages/cli typecheck: 0 errors. CLI surface and agent guidance are unchanged, so no regeneration was needed.0e17a563(714 s) and again at80c9b1fe(555 s). The HACK-1318/1414/1427/1273 group passes 8/10. The two failures,...keeps it loadable until rollback frees it (HACK-1273)andv3 value-free env override binds the engine config hash... (HACK-1273), time out at their fixed 20 s budget insidestore.prepare, before any changed path. They fail the same way on the unchanged PR head2adbd9dcon this host, so they are load-sensitive (HACK-1367), not a regression. For local runs only, the named budget was raised so that the load could not end a test. The committed value is 240 s.0e17a563(run 40124835555): all 20 checks green, includingtestshards 1-4/4, docker-e2e shards 1-4/4, Candidate core (macos-latest, ubuntu-latest), Config compiler (4 targets), Runtime state models, linux-process-lifetime, runtime-images and secret-scan.80c9b1fe: all 20 checks green (run 40128748032), with the same set as above. After that run,nextmoved three more commits to246f1b4e(test(e2e): prove network probe budget and resolved-address fetch (HACK-1428) #484, test(runtime-core): prove claim-writer serialization without a per-writer 3 s budget #485 and fix(runtime-core): name a node job's macOS boot by its session UUID, not calendar boot time #486: runtime-core and e2e tests, none in this change's files).git merge-treemerges them cleanly. They are not merged here, so GitHub reports BEHIND again.Remaining limits. Rollback still refuses when something it checks has changed: a running original, an edited held original or candidate, changed resources, changed managed env layers, the runtime override, or conflicting bytes at an original path. That refusal keeps the journal and evidence. Rollback keeps the managed-revision proof. Hack's own
hack env setrefuses while a switch is pending: a temporary probe, not committed, showed it fails with the adoption selection refusal. A managed env layer file edited outside Hack during a pending switch may still leave a similar dead end, because--recover --rollbackjournalsrolling-backbefore it reads its inputs. That case is not reproduced here and is reported as a lower-severity follow-up finding.Refs HACK-1437
🤖 Generated with Claude Code