Repository navigation
feat(upgrade): read-only upgrade check and snapshot/rollback library (HACK-2189) - #790
Conversation
…2189) A step that changes files snapshots them first, records what it wrote, and can put every file back. Bytes are kept in a private directory outside every working tree (0700/0600, no-follow, owner-checked) as content- addressed blobs; the manifest holds only paths, labels, kinds, sizes, modes and SHA-256 digests. Restore puts back exact bytes and modes, refuses a target that changed since the step recorded it unless told to overwrite, keeps whatever it replaces or removes, and reports every target it could not restore. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Snapshot and rollback API, for HACK-2191 (env migration) and a later import {
createUpgradeSnapshot, // before the step: snapshot every file it may touch (all or nothing)
recordUpgradeSnapshotAfter, // after the step (or after each write): record what it wrote
restoreUpgradeSnapshot, // put everything back
readUpgradeSnapshot, listUpgradeSnapshots, defaultUpgradeSnapshotRoot,
} from "../lib/upgrade-snapshot.ts";
// 1. Before changing anything. List every path the step may write, create or remove:
// a path that does not exist yet is recorded as absent.
const created = await createUpgradeSnapshot({
purpose: "env-migration app-a", // no values
targets: [
{ path: "/abs/repo/.hack/hack.env.json", label: "project app-a: hack.env.json" },
{ path: "/abs/repo/.hack/.env", label: "project app-a: .hack/.env" },
{ path: "/abs/repo/.hack/hack.env.yaml", label: "project app-a: hack.env.yaml" }, // absent now
],
// root?: string default ~/.hack/upgrade/snapshots (private, outside any Git working tree)
});
if (created.status !== "created") {
// { status: "refused", reason: "targets", targets: [{ path, label, reason }] }
// reason: not-absolute | duplicate | symlink | not-a-file | too-large | unreadable | inside-snapshot-root
// { status: "refused", reason: "root", root, rootReason }
// rootReason: inside-working-tree | not-private | not-owned | symlink | not-a-directory | unwritable
// Nothing was written. Do not run the step.
}
const { manifestPath } = created.snapshot; // .../<id>/manifest.json
// 2. Run the step, then record what it wrote (all targets, or `paths`).
await recordUpgradeSnapshotAfter({ manifestPath /*, paths?: string[] */ });
// -> { status: "recorded" } | { status: "invalid-manifest", message } | { status: "refused", paths: [{ path, reason }] }
// 3. Roll back.
const result = await restoreUpgradeSnapshot({ manifestPath /*, overwrite?: boolean, paths?: string[] */ });
// -> { status: "invalid-manifest", message }
// | { status: "restored" | "incomplete", results: [{ path, label, status, keptAs?, message? }] }Per-target restore statuses
Guarantees you can rely on
Commands on top of this ( |
…mmands (HACK-2189) hack upgrade check [--json] reads the machine and every registered project and ends with an ordered plan and the single next command: installed versions and channel, the daemon's owner and login service, the projects registry (dead entries counted apart from unreadable ones), global services, proxy ports, Linux DNS, the global secret store and key, and per live project the env format, env key, tracked machine-local files, ignore rules, agent integrations and (Apple Silicon) native preflight. It changes nothing, prints no value, and exits 1 naming the blockers. Host probes are injected and time-limited; HACK_HOST_PROBES_DISABLE=1 (set by the test preload) refuses them all. hack upgrade snapshot keeps the files the upgrade changes through the snapshot library; hack upgrade rollback --rollback-manifest restores them, refusing changed files unless --overwrite, and lists snapshots without a manifest. The native preflight report can now be collected without printing it or running the native runtime review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…; keep the check under 30 s (HACK-2189) An entry is dead only when its checkout or .hack/ is gone, or none of hack.config.json, hack.toml and docker-compose.yml remains (the inputs project discovery and prune accept). Project checks share a deadline: no check starts after 24 s, and those left report "not checked". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
Security findingsAdvisory findings (1)ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
HACK-2191 is binding to the posted snapshot API behind a small adapter. One additional contract would help keep cleanup inside the snapshot owner: can you export a read-only coverage verifier that accepts a manifest and selected paths and validates each original blob as well as the manifest? |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b011e0c944
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…rint the running CLI (HACK-2189) - Each registered linked worktree is classified and checked on its own, so a broken worktree blocks even behind a healthy primary checkout. - hack upgrade snapshot records the env migration's write set: the Compose file it repairs and the YAML layers it creates (absent ones as absent), so a rollback also moves created layers aside. - Printed commands run the CLI that produced them: during the prerelease the hack-next launcher (with HACK_RUNTIME_BACKEND=compose for a Compose run), not the stable hack on PATH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f77d7b0c80
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… tree (HACK-2189) The working-tree check walked only the path as written, so a symlinked ancestor (a hack home inside a dotfiles checkout) carried snapshot blobs into a repository. The root is now checked as written, as resolved before it is created, and again on the created directory before anything is written into it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ab1e18769a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
…ry required check (HACK-2189) Second-family review of #790: - Restore moves the current entry to an invocation-owned sibling with one rename, keeps exactly those bytes, and publishes with a hard link that refuses an occupied target; a late edit is linked back and kept. - The snapshot takes the migration's own overlay selection, including overlays only the secret store holds, and reports a gap when listing them would need the OS keychain. - The global key is audited with the runtime's own reader (link, mode, Git tracking); no store is not a lost key; an env key is tested without exposing it; a key only the keychain may hold is incomplete, not gone. - A live checkout that cannot be read blocks; a required check that fails or runs out of time is incomplete, and incomplete means not ready. - One deadline covers registry discovery, classification, probes and checks; config inputs must be regular files (no-follow, non-blocking, bounded); child processes inherit the check's deadline through withProcessDeadline, and the shared git probes gain time limits. - Commands are built from a prefix and separately quoted arguments, never by rewriting text; plan steps carry a command or a manual action. - The snapshot step comes first; unknown restore paths are rejected; the manifest directory is fsynced; the single-writer rule is documented. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
…ent (HACK-2189) tests/fixture-hermeticity.test.ts requires every fixture git spawn to use fixtureGitEnv; the upgrade-check fixtures set their own env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3118605844
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…led Git listing is incomplete (HACK-2189) - If the snapshot cannot keep the displaced file's bytes (a full disk), the file is linked back to its path; if another file took the name meanwhile, it stays at its sibling path and the result names it. - tracked-files: Git failing on a checkout (unreadable index, refused repository) is incomplete, not "not a Git checkout", so readiness is not claimed without knowing whether sensitive files are tracked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
|
@codex review |
|
HACK-2191 integration note: env rollback now deliberately retains project keys (including a newly created git-common-dir key), because another checkout may already use one for its ciphertext. It also retains the corresponding root ignore entry when needed. Its snapshot adapter filters these targets and restores only journal-guarded selected paths. Please keep this invariant in the generic rollback entry point too: a manifest with purpose The env adapter also supports an explicit The small adapter is ready to bind statically once this library lands on |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0733d15c45
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…-2189) A project may select the global store with its own keyPath. The global store finding now checks the key each reader's configuration selects (this shell's HACK_SECRETS_FILE_KEY first, as at runtime): readers whose own key opens the store are not blocked, and retiring is not suggested while another configured key opens it (retire-store refuses then). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 049249eb01
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… gap (HACK-2189) A live checkout whose .hack/ can be searched but not listed got an empty inventory, so its dotenv files and layers were silently left out while the snapshot claimed to be complete. The listing failure is now a coverage gap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0614bc4b23
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…HACK-2189) The env key check audits the key file's hygiene only, so encrypted env layer values with no key, or a wrong key, passed as "none needed". A new required checkout check, env-secrets, uses the read-only, value-free inspectProjectEnvLayerKeys: secrets with no key available, or that do not all open, block; a layer that cannot be inspected is incomplete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 64fa4716d1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…oes back in place (HACK-2189) A regular target that became unreadable or too large between the checks and the displacement was treated as a non-file and left in displaced/. It is now a late edit: linked back to its path (no-clobber) and reported as a conflict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
🛡️ Codex Security Review · Automatically triggered
Here are some automated security review suggestions for this pull request.
Reviewed commit: b97c9f0538
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
…t (HACK-2189) GIT_DIR or GIT_WORK_TREE can make any directory (for example $HOME) part of a working tree with no .git marker above it, so the snapshot root's working-tree check passed and secret blobs could be staged by git add. With either set, creating a snapshot is refused (inside-working-tree) and a restore writes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
…e included (HACK-2189) Trimming treated GIT_DIR=' ' as unset, though Git takes it as a literal path. A defined value of any kind, empty included, now fails closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. 🚀 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Snapshot and rollback library: current public interface (at c0f7d1e). This replaces the earlier API comment and is the version HACK-2191, HACK-2213/2216 and HACK-2214 should code against. Module: import {
createUpgradeSnapshot, // before a step: snapshot every path it may write, create or remove
recordUpgradeSnapshotAfter, // after the step (or after each write): record what it wrote
restoreUpgradeSnapshot, // roll back
readUpgradeSnapshot, // validate and read one manifest
listUpgradeSnapshots, // newest first
defaultUpgradeSnapshotRoot, // <hack home>/upgrade/snapshots
MAX_SNAPSHOT_FILE_BYTES, // 64 MiB per file
} from "../lib/upgrade-snapshot.ts";
createUpgradeSnapshot({
purpose: string, // e.g. "env-migration app-a" (no values)
targets: readonly { path: string; label: string }[], // absolute paths; labels hold no values
root?: string, // default defaultUpgradeSnapshotRoot()
now?: Date, id?: string, // test seams
}) → { status: "created", snapshot: { id, dir, manifestPath, manifest } }
| { status: "refused", reason: "targets", targets: [{ path, label, reason }] }
// reason: not-absolute | duplicate | symlink | not-a-file | too-large | unreadable | inside-snapshot-root
| { status: "refused", reason: "root", root, rootReason }
// rootReason: inside-working-tree | not-private | not-owned | symlink | not-a-directory | unwritable
recordUpgradeSnapshotAfter({ manifestPath, paths?: string[], now? })
→ { status: "recorded", snapshot } | { status: "invalid-manifest", message }
| { status: "refused", paths: [{ path, reason }] } // throws on a path that is not a target
restoreUpgradeSnapshot({ manifestPath, overwrite?: boolean, paths?: string[] })
→ { status: "invalid-manifest", message }
| { status: "restored" | "incomplete", results: [{ path, label, status, keptAs?, message? }] }
// throws on a path that is not a targetWhat a snapshot holds: a private directory
All snapshot creation is all or nothing: a refused target refuses the whole snapshot and nothing is written. A path that doesn't exist yet is recorded as How rollback is called and behaves:
The CLI on top:
Not covered yet (for HACK-2213/2216 to add through this interface): the snapshot does not cover:
To include the unit files, pass them as targets, absent ones included, before rewriting a unit. A file-level rollback restores the unit's bytes only: reloading or restarting the service manager after a restore is the caller's step. Daemon runtime state such as pid and socket is not meaningful to restore; a caller should stop or restart the daemon instead. |
|
Ready for lead: head c0f7d1e (HACK-2189). This includes next b1e123b via normal merges.
Not merged. |
## Summary Unit 1 of the minimal native desktop app (HACK-2214): a design document only. No code, no change outside `apps/desktop/`. `apps/desktop/DESIGN.md` covers: 1. Every view and its states as tables: project list, detail header per project state, Services, Logs, Env (names only), Activity, status bar. 2. First-run onboarding: checks in order, which need admin rights, system prompt vs terminal hand-off (recommendation: terminal hand-off on both platforms for v0), what a skipped step shows. 3. Doctor cards grouped by area, machine findings shown once. 4. Upgrades over `hack update --check` and the #790 `hack upgrade check|snapshot|rollback` contract, with the daemon restart (HACK-2216) and failure states. 5. Daemon states with one action each. 6. Always-on (HACK-2213): states and the exact fields and commands needed from the CLI side. 7. Data contract per screen (command or endpoint, interval, timeout) and 12 numbered CLI/daemon requests, each one PR. 8. Scope: v0, v0.1, later, never. 9. Distribution: Homebrew cask beside the formula, signing and notarization steps (nothing set up), no auto-update, Linux tarball, minimum OS versions. Diagrams are Mermaid sources in `apps/desktop/diagrams/*.mmd`, rendered to SVG with the pinned Pele renderer and embedded in the doc. ```mermaid flowchart LR App["Hack desktop app"] -->|"hack … --json (as the user)"| CLI["hack CLI"] App -->|"GET /v1/status, /v1/projects"| D["hackd socket"] CLI --> D CLI --> E["Engine"] App -.->|"admin steps"| T["Terminal"] ``` ## Validation - `bun run privacy:check`: ok. - All six diagrams render with `pele-render` (no errors). - The data contract was read from source on this branch (`src/daemon/server.ts`, `src/commands/*`, PR #790); no `--json` command was run against a live daemon or Docker. ## Not in this PR The crates (`apps/desktop/core`, `apps/desktop/app`) follow in a separate PR (`feat/desktop-app-core`). 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- codesmith:footer --> --- <a href="https://backend.blacksmith.sh/track/run-proof?expires=1797205571&installation_model_id=17053&pr_number=800&ref=codesmith_pr_footer&repository=hack-dance%2Fhack&return_to=https%3A%2F%2Fgithub.com%2Fhack-dance%2Fhack%2Fpull%2F800&variant=run_proof&signature=21c30054e774d8aaffbe431ce236d2ee8497fdacef2aa734bef4f98a9968595a"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-run-proof-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-run-proof-light.svg"><img alt="Run Proof" src="https://pr-comments-assets.blacksmith.sh/codesmith/proof-run-proof-dark-3x.png" width="102" height="28"></picture></a> <sup><a href="https://app.blacksmith.sh/hack-dance/codesmith/hack/pr/800?autoLogin=true&ref=codesmith_pr_footer">Open in [code]smith</a> · Need help on this PR? Tag <code>@codesmith-bot</code> with what you need.</sup> <!-- codesmith:autofix:disabled --> <!-- /codesmith:footer --> --------- Co-authored-by: Dimitri Kennedy <344842+roodboi@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The next.17 section omitted the tmux breaking-change warning that already existed at the page tail. Move it into the release section and put its v4 stop procedure before the v5 trial. The env breaking entry was already correct; keep its scoped single-key/terminal/stdin migration guidance. Audit all **71 commits** from `v5.0.0-next.16` (`b21c3b1898`) through candidate **`5002cf87bb50686b17ceae8a4c835f524ee1b3a5`** against merged code and PR text. Add missing upgrade-assistant, activity, observe/plan/version, interactive exec, recovery, background-command and host/secret-custody notes. Preserve the complete native env-file behavior table and mark the four new JSON contracts **shape may change before 5.0.0**, with the planned HACK-2238 envelope migration. Correct stale native interactive-exec/network/metadata/Ctrl-C claims and the short upgrade assistant wording. Credit the publisher-shutdown fix to #719, already in next.16; keep next.17 lifecycle-command retirement separate. Retake a private stopped-state backup after v4 stops zellij, keeping the first copy too. Keep next.16 warnings scoped to that bundle. Keep original-key backups before older Doctor migrations and distinguish file rollback from runtime/data recovery. Only eight Markdown files change. Runtime, tests, workflows, generated CLI reference, diagrams, immutable snapshots and the site pin are unchanged. No diagram changed, so a new visual would add nothing. The site re-pin remains a separate release follow-up. Merge timing: respect the lead's candidate freeze; intended for the batch immediately after next.17 publication and the site re-pin. The moved tmux breaking-change notice is called out for the lead's pre-cut decision. This PR does not merge or publish anything. Validation at **`d95cfc554fabc4a0d70e3ba6caeb6746f63a198c`**: - Bun 1.4.4, frozen dependencies. Docs prepare/check/typecheck/build and site tests (2 passed, 25 assertions) pass. Production-mode HTTP smoke: 97 docs, 82 SVGs, 217 local links/anchors; task server stopped. - All 50 Hack commands named anywhere in the notes exist in the current generated CLI reference; 19 option contracts checked. CLI-reference regeneration has no diff. - All 71 commits have one row below, including the test commit and merge for #786. All 70 source evidence paths exist at the fixed candidate. Source release-note local links/anchors pass. - Privacy and complete reachable-history Gitleaks pass (1,330 commits, no findings). Persistent Claude Opus second-family review is CLEAN at this exact head. Hosted run 43585059152 passed docs site, secret scan and every planned Linux unit shard (4–8), plus the required test fan-in. Typecheck/privacy/quality ran on shard 8. Focus had no selected tests. Compiler, native core, runtime images/models, process-lifetime and Docker execution were out of scope; a green Docker fan-in is not Docker execution evidence. All bot comments were read; CLEAR at d95cfc5, zero unresolved threads/change requests. Codex code/security reviews found no issues. - No real install, migration, daemon/DNS/trust change, Docker or native VM run was performed for this documentation audit. Native acceptance and the candidate's hosted release qualification remain separate. ## Candidate commit coverage `entry present` means the behavior already had an entry within the next.17 section (some now have fuller detail). `added` means missing behavior or migration guidance was added there; #824 moves the existing warning into that section. `not user-visible` means no shipped CLI/runtime change: documentation, test/CI/repository housekeeping or the internal desktop preview. Source links pin the candidate, rather than a moving branch. | Commit / PR | Coverage | Entry or reason | Audited source | | --- | --- | --- | --- | | [70d328e](70d328e) [#743](#743) | added | Host setup: privileged/NSS safeguards | [src/lib/linux-trust-setup.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/linux-trust-setup.ts) | | [51f15c8](51f15c8) [#746](#746) | not user-visible | Docs-only upgrade checklist and site generation; no installed behavior | [scripts/docs/prepare.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/docs/prepare.ts) | | [c14f3f5](c14f3f5) [#744](#744) | added | Native setup JSON accounting | [packages/runtime-core/src/provider/setup.rs](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/packages/runtime-core/src/provider/setup.rs) | | [b0c6bcd](b0c6bcd) [#705](#705) | added | Retained background exec/run and cursor tools | [src/commands/background-command-start.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/background-command-start.ts) | | [c89f4fd](c89f4fd) [#745](#745) | added | Linux daemon query/home refusals | [src/commands/daemon-systemd.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/daemon-systemd.ts) | | [cce4b59](cce4b59) [#752](#752) | not user-visible | Site pin/snapshots and editorial pages only | [scripts/docs/versions.json](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/docs/versions.json) | | [56676fc](56676fc) [#754](#754) | added | Setup reuse/offline accounting; pin-watch changes are internal | [packages/runtime-core/src/provider/setup.rs](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/packages/runtime-core/src/provider/setup.rs) | | [7a496f1](7a496f1) [#755](#755) | added | MCP registered selectors and background tool guidance | [src/mcp/server.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/mcp/server.ts) | | [e7a7039](e7a7039) [#747](#747) | entry present | Native follow text shape; added session ends/restart recovery | [src/backends/native-log-follow.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-log-follow.ts) | | [87a6df5](87a6df5) [#758](#758) | added | Compose short one-shot startup | [src/lib/compose-startup-state.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/compose-startup-state.ts) | | [2afb5c4](2afb5c4) [#761](#761) | added | Native Hack networks and inert generated labels | [src/lib/native-hack-compose-conventions.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/native-hack-compose-conventions.ts) | | [342a585](342a585) [#771](#771) | not user-visible | Docs-only next.16 trial record | [docs/versions/v5/guides/upgrade-from-v4.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/versions/v5/guides/upgrade-from-v4.md) | | [6f0acf1](6f0acf1) [#769](#769) | entry present | Migration live-key/alias preservation | [src/lib/project-env-config.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-env-config.ts) | | [c96c444](c96c444) [#756](#756) | added | Child key filtering, fixed diagnostics, legacy ignore/YAML API refusals | [src/lib/child-environment.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/child-environment.ts) | | [e2c73c6](e2c73c6) [#748](#748) | not user-visible | Contributor CI selection/coalescing, outside product release notes | [scripts/ci-change-scope.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/ci-change-scope.ts) | | [ebf794e](ebf794e) [#760](#760) | added | Agent/output custody warning; runtime unchanged | [src/agents/instruction-source.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/agents/instruction-source.ts) | | [021b16b](021b16b) [#763](#763) | added | One-pass background redaction scan; no public benchmark claim | [src/lib/background-command/output-scanner.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/background-command/output-scanner.ts) | | [8564ec4](8564ec4) [#765](#765) | added | Shared preflight/start admission and detached failure reporting | [src/backends/native-project-review.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-project-review.ts) | | [0c0fba2](0c0fba2) [#767](#767) | entry present | Breaking env exposure/protect/stdin/scope/API guidance | [src/commands/env.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/env.ts) | | [9435da9](9435da9) [#762](#762) | added | Local Git/link refusals and no-follow dotenv | [src/lib/project-env-config.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-env-config.ts) | | [edf23ae](edf23ae) [#753](#753) | added | Daemon stop proves the live Hack home | [src/commands/daemon-systemd.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/daemon-systemd.ts) | | [573ceec](573ceec) [#766](#766) | entry present | Doctor layer/integrity error; added store/key write refusals | [src/lib/secret-store.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/secret-store.ts) | | [a0e9760](a0e9760) [#757](#757) | added | Orphan output truncation and final-sync evidence | [src/lib/background-command/recovery.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/background-command/recovery.ts) | | [10e1958](10e1958) [#759](#759) | added | Private lifecycle logs/FIFO and expiring mux payloads | [src/lib/private-file.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/private-file.ts) | | [bd899ee](bd899ee) [#775](#775) | not user-visible | Portable test fixtures only | [tests/single-file-tar.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/single-file-tar.test.ts) | | [80dae3d](80dae3d) [#776](#776) | added | Candidate missing-daemon diagnosis is optional/healthy | [src/commands/doctor.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/doctor.ts) | | [5b2a2d0](5b2a2d0) [#772](#772) | added | Bounded attaching-network retry; strict saved reads remain | [src/lib/native-compose-ownership.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/native-compose-ownership.ts) | | [ab29d49](ab29d49) [#777](#777) | not user-visible | CI scheduling only | [docs/reference/ci-test-selection.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/reference/ci-test-selection.md) | | [31339c7](31339c7) [#770](#770) | entry present | Native inferred readiness, 240 s, HTTP/service_healthy limits | [packages/runtime-core/src/project/compose/service.rs](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/packages/runtime-core/src/project/compose/service.rs) | | [156ee7f](156ee7f) [#773](#773) | entry present | Loopback proxy port/Doctor/open checks | [src/lib/proxy-loopback.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/proxy-loopback.ts) | | [a7ec3c7](a7ec3c7) [#768](#768) | entry present | Cleanup receipts/partial outcomes and tracked-file wording | [src/lib/project-env-config.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-env-config.ts) | | [ceb3e23](ceb3e23) [#778](#778) | entry present | Native bridge socket readiness | [packages/runtime-core/src/provider/bridge.rs](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/packages/runtime-core/src/provider/bridge.rs) | | [af91049](af91049) [#783](#783) | entry present | Pointer reconciliation after partial cleanup | [src/commands/doctor.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/doctor.ts) | | [0a0bfdb](0a0bfdb) [#780](#780) | not user-visible | Site-only Linux guide publication; no CLI behavior | [scripts/docs/versions.json](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/docs/versions.json) | | [d0cf767](d0cf767) [#786](#786) | not user-visible | Consent test wording only (both commits) | [tests/env-migration-tracked-consent.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/env-migration-tracked-consent.test.ts) | | [7e2e630](7e2e630) [#786](#786) | not user-visible | Consent test wording only (both commits) | [tests/env-migration-tracked-consent.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/env-migration-tracked-consent.test.ts) | | [91c311e](91c311e) [#779](#779) | entry present | Restore or reversible retire; missing store keys scoped to readers | [src/lib/secret-store-retire.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/secret-store-retire.ts) | | [6ab1ace](6ab1ace) [#785](#785) | not user-visible | Docs-only release/upgrade/env notes | [docs/guides/v5-release-notes.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/guides/v5-release-notes.md) | | [25bd63c](25bd63c) [#764](#764) | entry present | Doctor hygiene, local modes, ignored staging and live-input protection | [src/lib/secrets-hygiene.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/secrets-hygiene.ts) | | [d9df57c](d9df57c) [#784](#784) | not user-visible | Resolver/proxy and consent test corrections only | [tests/project-open-loopback.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/project-open-loopback.test.ts) | | [2c1f665](2c1f665) [#791](#791) | added | Detached failure names, open DNS hint, init placeholder; prune guidance | [src/commands/project.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/project.ts) | | [f6fe55f](f6fe55f) [#787](#787) | not user-visible | Docs-only retirement/hygiene follow-up | [docs/guides/v5-release-notes.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/guides/v5-release-notes.md) | | [7f9548c](7f9548c) [#792](#792) | not user-visible | Start-here docs, navigation and contributor rule only | [docs/versions/v5/install.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/versions/v5/install.md) | | [a0cfc73](a0cfc73) [#793](#793) | added | Verified updater publication, backups and reversible candidate removal | [src/lib/self-update.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/self-update.ts) | | [292d312](292d312) [#782](#782) | added | Lock-free native ps and proved failed-start down | [src/backends/native-project-observe.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-project-observe.ts) | | [b1e123b](b1e123b) [#796](#796) | added | Doctor reader count/worktree grouping in store recovery | [src/lib/doctor-secret-integrity.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/doctor-secret-integrity.ts) | | [286d00f](286d00f) [#789](#789) | added | Foreground command-before-wrapper retirement | [src/lib/project-lifecycle-processes.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-lifecycle-processes.ts) | | [bb6a7d1](bb6a7d1) [#788](#788) | not user-visible | Linux qualification runbook only | [docs/guides/linux-host-qualification.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/guides/linux-host-qualification.md) | | [a262d42](a262d42) [#800](#800) | not user-visible | Internal desktop design; outside supported CLI release scope | [apps/desktop/DESIGN.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/apps/desktop/DESIGN.md) | | [6b161ba](6b161ba) [#774](#774) | added | Native interactive exec, finite fallback and receipt recovery | [src/backends/native-project-exec.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-project-exec.ts) | | [1249272](1249272) [#799](#799) | not user-visible | Portable output-contract CI selection only | [scripts/test-shard-plan.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/test-shard-plan.ts) | | [fa3f03d](fa3f03d) [#790](#790) | added | Read-only upgrade check, snapshot and rollback | [src/commands/upgrade.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/upgrade.ts) | | [c3b79bc](c3b79bc) [#807](#807) | added | Observe-only global status and unknown evidence | [src/commands/global-status-observe.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/global-status-observe.ts) | | [29be043](29be043) [#812](#812) | added | Read-only local CA trust diagnosis | [src/lib/ca-trust-status.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/ca-trust-status.ts) | | [f0102fd](f0102fd) [#814](#814) | not user-visible | Prepared-base verifier test fixture only | [tests/qualify-provider-pin.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/qualify-provider-pin.test.ts) | | [b4ebfec](b4ebfec) [#815](#815) | added | Compact upgrade plan, snapshot-backed apply/resume | [src/lib/upgrade-apply.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/upgrade-apply.ts) | | [c44b266](c44b266) [#801](#801) | not user-visible | Internal optional desktop preview; not shipped CLI behavior | [apps/desktop/Cargo.toml](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/apps/desktop/Cargo.toml) | | [56d1d0f](56d1d0f) [#804](#804) | added | Automatic mux sweep preserves live/retired config inputs | [src/lib/live-inputs.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/live-inputs.ts) | | [1958363](1958363) [#805](#805) | added | MCP timedOut result and Codex installer timeout | [src/mcp/server.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/mcp/server.ts) | | [e6207e8](e6207e8) [#810](#810) | added | Lifecycle activity writes including handler refusals; activity JSON | [src/commands/lifecycle-activity.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/lifecycle-activity.ts) | | [23fe08f](23fe08f) [#825](#825) | added | Read-only batch review and permission-only key repair | [src/lib/upgrade-review.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/upgrade-review.ts) | | [cab596c](cab596c) [#781](#781) | entry present | Full native env-file behavior table; added support/precedence summary | [src/backends/native-compose-environment.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-compose-environment.ts) | | [1e84684](1e84684) [#798](#798) | not user-visible | Rust test process isolation/nextest; internal qualification only | [scripts/test-runtime-core.sh](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/test-runtime-core.sh) | | [518af29](518af29) [#817](#817) | not user-visible | Remove accidental empty checkout files; no installed CLI change | [.gitignore](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/.gitignore) | | [07192f7](07192f7) [#824](#824) | added | BREAKING warning moved from page tail into next.17; v4 stop procedure retained | [src/mux/mux-config.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/mux/mux-config.ts) | | [fe09a5b](fe09a5b) [#816](#816) | added | Read-only install/trust admin plans and exits | [src/commands/global.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/global.ts) | | [8977676](8977676) [#827](#827) | added | Registry-preserving project observe, runtime opt-in | [src/commands/projects.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/projects.ts) | | [5965b23](5965b23) [#828](#828) | added | Running CLI version identity JSON | [src/lib/version-report.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/version-report.ts) | | [2922210](2922210) [#830](#830) | added | Upgrade safe/admin/manual action kinds | [src/lib/upgrade-action-kind.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/upgrade-action-kind.ts) | | [51b55c0](51b55c0) [#820](#820) | not user-visible | Archive/plan/shared-helper verification fixtures only | [tests/python-free-download.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/python-free-download.test.ts) | | [5002cf8](5002cf8) [#809](#809) | added | Retirement read retry, second Ctrl-C and native cancellation cleanup | [src/lib/project-lifecycle-processes.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-lifecycle-processes.ts) | --------- Co-authored-by: Kit <344842+roodboi@users.noreply.github.com>
Summary
HACK-2189 is the first unit of the upgrade assistant (HACK-2188) and the foundation the later units build on. The goal: moving a machine from v4 to v5 becomes one guided, resumable and reversible flow, replacing a hand-written rollback sheet. This PR adds a read-only check, a snapshot and rollback library, and three commands on top of them.
hack upgrade check [--json]It checks the machine and every registered project.
Machine:
hackon PATH: its version and install kind..hack/, or all ofhack.config.json,hack.tomlanddocker-compose.ymlare gone) are counted apart from live entries that cannot be read. Dead ones point athack projects prune --dry-run.Per live checkout (each project's primary checkout and each registered linked worktree):
.hack/.gitignore.hack setupstate).Output: an ordered plan and the single next command. Blockers come first, then
hack upgrade snapshot, then machine steps, then project steps. The command exits 0 when nothing blocks. Otherwise it exits 1 and names the blockers. A blocker is something that would make the upgrade fail or lose data:Changes nothing: no file, registry, service, container or key is written, started or provisioned, and nothing prompts. A test compares the whole fixture tree (paths, modes, digests) before and after. Doctor functions with side effects are avoided:
globalStatus/ensureDockerRunningcan start Docker;hack projectstouches the registry;collectProjectNativePreflightis split out of the doctor command so the report can be collected without printing it or running the runtime review.Fast: dead entries cost one
stat. Live projects are checked eight at a time, with an 8-second limit per check and a shared 24-second budget. When the budget runs out, the remaining checks report "not checked" rather than running. Host probes have time limits too. An 80-entry fake registry (76 dead) finished its checks in about 130 ms.Hermetic:
hackand native preflight.HACK_HOST_PROBES_DISABLE=1refuses them all and is set by the test preload, likeHACK_SYSTEMD_DISABLE.Snapshot and rollback library:
src/lib/upgrade-snapshot.tsThe API is posted in a comment for HACK-2191: #790 (comment). It is modelled on the
hack setup migratebackup manifest, with these differences:file|absent), size, mode and SHA-256, before and after. Bytes live in content-addressedblobs/<sha256>, because env files hold secrets.recordUpgradeSnapshotAfter) unless told to overwrite. A target with no recorded after state also needsoverwrite.keptAs.Commands
hack upgrade snapshot [--json]keeps:.hack/config, env contract, Compose file, dotenv files, YAML layers (the base, machine-local and per-overlay layers it would create, recorded as absent when they don't exist yet),.hack/.gitignoreand in-checkout env key files.It skips links. Printed commands run the CLI that produced them: during the prerelease, the
hack-nextlauncher, withHACK_RUNTIME_BACKEND=composefor a Compose run.hack upgrade rollback --rollback-manifest <manifest> [--overwrite] [--json]restores the files. Without a manifest it lists the snapshots and changes nothing, because non-interactive mode never guesses.Docs
docs/cli.mdlists the commands.docs/reference/cli.mdis regenerated.Validation
tests/upgrade-snapshot.test.tsandtests/upgrade-check.test.ts(19, all passing), using a temporary HOME. No Docker, no daemon autostart, no real home or registry. They cover:checkexits 1 naming blockers, and snapshot → mutate → rollback refuses without--overwriteand restores with it.nextas well, plus the unhandled error it leaves behind.checkat the 44-warning baseline (none in touched files);docs:preparethendocs:checkpass; privacy ok; gitleaks clean.Review round 2 (head 3118605): what changed
readyneeds no blockers and noincompletechecks. A required check that fails or times out, disabled host probes, a key only the keychain could confirm, or a registry cut short by the deadline are all incomplete. The exit code is 1 when not ready. A live checkout whose config cannot be read blocks.commandor a manualaction.next_commandis null when the first step is manual, andnext_actionthen says what to do. The snapshot step now comes first.inspectStoreKeyStatefollows the runtime's order and refusals (env key, then the key file through the runtime's reader: link, mode, Git tracking). It never asks the keychain. An absent store is not a lost key.withProcessDeadline(AsyncLocalStorage) capsexecand the Git probes, so every child a check starts ends with that check. Previously unbounded Git calls in shared helpers (readGitPath, the generated-filesls-files, three ignore/tracking probes) now have 10 s limits, which helps doctor too.nextcheckout on this host (adopted-project doctor tests, registry lock timing).Limits and follow-ups
hack upgrade snapshotdoes not cover the daemon's unit or plist or Docker state; the guide says so.hack doctor --path <root>and the remedy is in the finding text.🤖 Generated with Claude Code