Skip to content

fix(env): keep secret stores and project env secrets readable under one key (HACK-2169) - #766

Merged
roodboi merged 16 commits into
nextfrom
claude/hack-2169-secret-store-integrity
Nov 13, 2026
Merged

roodboi merged 16 commits into
nextfrom
claude/hack-2169-secret-store-integrity

Conversation

@roodboi

@roodboi roodboi commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes HACK-2169 (env/secret audit items F7, F8, F9 and S3) and HACK-2036 (key file hygiene), and adds a hack doctor migration path for setups those bugs already damaged.

What changes

Encrypted secret store (encrypted_file, and the cloud shim)

  • F7: a store that exists but cannot be read is never treated as empty. Every reader classifies the store through one no-follow read: a symlinked, non-regular or oversized store is refused, and any other read failure raises SecretStoreUnreadableError. So a set can never replace an unreadable store.
  • F9: the process-wide key cache holds only keychain material. A key read from one store's key file, or from the environment, never stands in for another store's key.
  • HACK-2036: the key file is read without following a link. It is refused when group or other users can read or write it (mode 0600 is required), or when git tracks it.
    • The tracking check runs git ls-files --literal-pathspecs --error-unmatch with a 5 s bound, and only when the key's real directory (symlinks resolved, as git does) is inside a work tree.
    • Inside a work tree where git cannot answer, the key is refused rather than guessed untracked.
  • S3, explicit provisioning (hack env backend --provision-key with HACK_SECRETS_FILE_KEY):
    • It never replaces a different existing key, or one it cannot read. A key path can serve more than one store (the default one is shared), and a key that does not open one store may be the only key for another. Changing a key is a rotation, which re-encrypts; the refusal says to move a key file aside yourself, keeping a copy, only once no store needs it.
    • With no key file, it writes the key only when the configured store is absent or empty, or opens under that key. An unreadable or symlinked store is refused, because a new key file could shadow the key that opens it today.
    • Every key writer (explicit provisioning, keychain persistence and the first-use key) holds the key file's own <key>.lock, and each write checks the file again just before it lands. A concurrent writer's key is kept, never overwritten.

Project env layers

  • F8: hack env add --secret and hack env set --secret refuse, before writing anything, in two cases:

    • HACK_ENV_SECRET_KEY is set but no .hack.secret.key exists (a new key file would shadow the environment key);
    • a secret already in the layer does not open under the key readers use.

    Replacing the one entry being set is allowed. Legacy env migration refuses the same way instead of creating a shadowing key.

hack doctor: new secret integrity check

  • It reports, without values:
    • a store that is unreadable or refused;
    • a store key that is missing or refused;
    • a key that does not open its store;
    • an env layer with no available key, or the wrong key;
    • a layer with secrets under more than one key.
  • It inspects every layer runtime can read, each read the way runtime reads it: committed and machine-local layers, local-only overlays, in-project symlinked layers, and, in a linked worktree, the primary checkout's inherited local layers.
  • For a mixed layer it names the secrets that do not open, with the --service, --env and --local options needed to address them. Recovery: read those values with the old key, hack env unset each one (unset needs no key), then hack env set --secret again. For inherited layers it says to run those steps in the primary checkout, and to fix a divergent key first (the "worktree keys" check).
  • Anything it cannot inspect is an error, never success: a layer that exists but cannot be read or parsed, a directory that cannot be listed, inheritance that cannot be resolved, or an inspection that throws.

Behavior changes to note in the release

  • An encrypted_file key file that group or other users can read is now refused until someone runs chmod 600. Hack has always written 0600, so this affects only key files made or changed by hand.
  • --provision-key with a different HACK_SECRETS_FILE_KEY no longer overwrites an existing key file; it refuses.
  • A key file inside a git work tree whose git cannot report tracking (git fails, is missing, or takes more than 5 s) is refused.

Shared files

Follow-up (not in this PR)

  • The project env key's own git-tracking check (the .hack.secret.key helper in project-env-config.ts) still passes the file name without --literal-pathspecs. The name is fixed, so the risk is low, but it should match the store key check.

Validation

  • An independent persistent reviewer was clean after 9 rounds. The lead's second-family review then ran three rounds and found the issues fixed in the last four commits; its final round is clean on the code at 9fc9e26f.
  • Every Codex finding (5) is fixed with a regression test, replied to and resolved.
  • Tests:
    • tests/secret-store.test.ts:
      • F7, F9, HACK-2036 (mode, symlink, tracked, a key reached through a symlinked directory, a git that cannot answer);
      • provisioning refusals, including a different key shared with another store, and unreadable or refused stores;
      • three concurrent provisions writing exactly one key;
      • a first-use bootstrap that waits for the key lock and adopts the key written meanwhile.
    • tests/project-env-secret-key-integrity.test.ts: F8.
    • tests/doctor-secret-integrity.test.ts, isolated from the real home and global config:
      • every finding is value-free;
      • mixed-key recovery runs end to end, with the old values read under the old key;
      • local-only, symlinked, legacy and inherited layers;
      • unreadable, unparseable and unlistable layers reported as errors.
    • tests/env-migration-safety.test.ts: the migration refusal.
  • Local gates (Bun 1.4.4): bun run typecheck; bun run check at the 39-warning baseline; privacy:check; gitleaks over origin/next..HEAD. 574 tests across the related env, secret, doctor, inheritance, migration, hermeticity and CI-selection suites pass.
stateDiagram-v2
    [*] --> Locked: --provision-key with HACK_SECRETS_FILE_KEY (holds <key>.lock)
    Locked --> Refused: key file unreadable
    Locked --> Refused: a different key is there
    Locked --> CheckStore: no key file, or an empty one
    Locked --> Write: the same key (0600 rewrite)
    CheckStore --> Refused: store unreadable or refused
    CheckStore --> Refused: store has entries the key does not open
    CheckStore --> Write: store absent or empty, or opens under the key
    Write --> Written: file unchanged just before the write lands
    Write --> Refused: another key landed meanwhile
Loading

🤖 Generated with Claude Code

roodboi and others added 3 commits November 13, 2026 06:43
…e key (HACK-2169)

From the env/secrets security audit (F7, F8, F9, S3; HACK-2036):

- F7: an encrypted store that exists but cannot be read is never treated
  as empty; reads and writes refuse it (SecretStoreUnreadableError)
  instead of letting the next set replace it and lose every entry.
- F8: env add/set --secret use hydration's key rules: refused when only
  HACK_ENV_SECRET_KEY is set (a new key file would shadow it) and when a
  secret already in the layer does not open under the readers' key; the
  entry being replaced is exempt. Checked before any key or layer write.
- F9: the process-wide key cache holds only keychain material, used where
  a keychain lookup happens; a key read from one store's key file never
  stands in for another store's configured key.
- S3: explicit --provision-key refuses to replace a different existing key
  while the store has entries (that is a rotation).
- HACK-2036: the encrypted_file key and store are read without following a
  link; the key is refused when others can read or write it or git tracks
  it.
- Migration path: hack doctor's new "secret integrity" check reports an
  unreadable store, a missing or refused store key, a key that does not
  open the store, and an env layer holding secrets under more than one
  key, each with value-free recovery steps.

Co-Authored-By: Claude <noreply@anthropic.com>
…r's recovery steps that work (HACK-2169)

- Key provisioning with HACK_SECRETS_FILE_KEY publishes exclusively when no
  key file exists, refuses a key file it cannot read, and replaces a
  different key only as a repair: under the store lock, when the store's
  entries open under the environment key and not the existing one.
- Doctor lists the secrets in a layer under more than one key and says to
  unset then set them, which works even when several do not open.
- The store listing reads the store the way use does, so a symlinked
  store is reported as refused; key_refused is reported only for the key
  file's own refusals.
- Legacy env migration refuses to create a key file that would shadow
  HACK_ENV_SECRET_KEY.
- git ls-files uses literal pathspecs for the key file name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a key that shadows the store's (HACK-2169)

- The doctor's env layer check inspects the effective machine-local
  default layer, including hack.env.default.local.yaml beside a legacy
  local overlay, so the --local it suggests acts on the file it reported.
- Provisioning HACK_SECRETS_FILE_KEY into an absent key file refuses when
  the store has entries that key does not open (an older keychain-key
  setup), since readers would use the new file instead.

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

vercel Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hack-docs Ready Ready Preview Nov 13, 2026 12:38pm UTC

Request Review

@roodboi

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@linear-code

linear-code Bot commented Nov 13, 2026

Copy link
Copy Markdown

HACK-2169

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-13T12:40:10.317469Z 680e9ad Manual request
🔒 Security Review ✅ Completed 2026-11-13T12:44:54.973935Z 680e9ad Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ca80e64386

ℹ️ 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".

Comment thread src/lib/project-env-config.ts Resolved
@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: ca80e64386

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ 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.

roodboi and others added 2 commits November 13, 2026 07:16
…er (HACK-2169)

`hack env set --env <name> --local` can create hack.env.<name>.local.yaml
with no committed overlay beside it; the secret integrity inspection only
visited local layers of committed overlays, so mixed or wrong keys there
went unreported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er (HACK-2169)

The integrity inspector keeps the first label for each layer path, so the
default local layer is never reported with --env local advice.

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d256b3b8d5

ℹ️ 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".

Comment thread src/lib/project-env-config.ts Resolved
…CK-2169)

The integrity inspection discovers overlay names from layer files and
in-project symlinks alike, and reads each layer with the runtime's layer
read, so a layer linked from inside the project is checked and one that
leads outside it is skipped as runtime would refuse it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: d256b3b8d5

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ 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.

…layer's advice alone (HACK-2169)

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 58fa9f6adb

ℹ️ 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".

Comment thread src/lib/project-env-config.ts Resolved
@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 58fa9f6adb

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ 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.

roodboi and others added 2 commits November 13, 2026 07:41
…ee inherits (HACK-2169)

In a linked worktree that inherits local inputs, runtime also reads the
primary checkout's local default and overlay layers. The integrity
inspection now lists those too, read with the same inheritance guards
(path assertion, no redirection, primary-local read), and the doctor
says such a layer lives in the primary checkout, where the hack env
steps must run.

Also routes the key-tracking test's git through the hermetic fixture
helper, which fixture-hermeticity requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… from the primary's (HACK-2169)

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 621b93b388

ℹ️ 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".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 621b93b388

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ 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.

@roodboi

roodboi commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor Author

Ready for lead: head 621b93b388a82b2805fdf6a238c26ddc109ca632

Review: the persistent reviewer took 9 rounds and is clean at 621b93b.

  • Round 1: 4 should-fix and 5 nits. Round 2: 2 nits. Rounds 4, 6 and 8: one nit each, all on the Codex follow-ups below.
  • Codex found 3 P2s; each is fixed with a regression test that fails without the fix, replied to and resolved:
    • local-only overlays (hack.env.<name>.local.yaml) were not inspected;
    • in-project symlinked layers were not inspected;
    • a linked worktree's inherited primary local layers were not inspected.
  • Codex's code review of 621b93b388 reported "Didn't find any major issues", and its automatic security review found nothing. computer pr review-status: CLEAR at 621b93b388, 0 unresolved bot threads. The tool lists Codex as having reviewed an earlier commit, but Codex's own comment names 621b93b388 as the reviewed commit.

Linux gates green at this head: Config compiler (ubuntu-latest: typecheck and lint), test shards 4 to 8, linux-process-lifetime, Runtime state models, Change scope, test focus, secret-scan, runtime-images and docs site.

Still pending: test shards 1 to 3 (macOS) and docker-e2e shards 1 to 4.

Changes since the PR description:

  • The doctor's layer inspection covers every layer runtime can read, each read the way runtime reads it:
    • local-only overlays;
    • in-project symlinked layers (links that lead outside the project, dangle or point to non-files are skipped);
    • a linked worktree's inherited primary local layers, read with the same inheritance guards. Findings on these say to run the hack env steps in the primary checkout, and to fix a divergent key first (the "worktree keys" check).
  • A test fix: the key-tracking test spawned git directly, which made fixture-hermeticity fail on shard 5 at 58fa9f6a. It now uses the hermetic fixture helper.

Shared files (unchanged from the description):

  • project-env-config.ts: the secret branch of setProjectEnvValue, the new read-only inspector and its helpers, and a pre-write check in the migration.
  • doctor.ts: one new secret integrity check, not in the hygiene section.
  • shell.ts, the env parse errors and the ignore list are untouched.

Follow-up (not in this PR): the project env key's own git-tracking check still lacks --literal-pathspecs.

You planned one more independent review before merge.

🤖 Generated with Claude Code

…ed layers as fine (HACK-2169)

Second-family review of #766:
- Provisioning never replaces a different existing key: a key path can
  serve several stores, and failing to open one is no evidence a key is
  unused. The repair branch is gone.
- Every provisioning branch holds the key file's own lock, and each key
  write (provisioning, keychain persistence, first-use bootstrap of an
  empty file) re-checks the file in beforeRename, so a stale observation
  never overwrites a key written meanwhile.
- With no key file, the key is written only when the store is confirmed
  absent or empty, or opens under it; an unreadable or refused store is
  refused.
- The integrity check reports an existing layer it cannot read or parse,
  a directory it cannot list, and unresolvable inheritance as errors, and
  the doctor check reports a thrown inspection as an error.
- The doctor tests run with their own HOME, HACK_HOME, global config and
  no key variables or keychain.
- Follow-ups: the git tracking probe is bounded (5 s), skipped outside any
  work tree, and refuses when git cannot answer inside one; the
  mixed-layer test reads the old values with the old key before
  re-entering them.

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cd9f8484ea

ℹ️ 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".

Comment thread src/lib/project-env-config.ts Resolved
…th check refuses (HACK-2169)

Resolving the primary checkout's local default layer checks its path;
a failure there is now an inspection failure, not an absent layer.

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 9fc9e26fc6

ℹ️ 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".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 9fc9e26fc6

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ 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.

…et-store-integrity

# Conflicts:
#	src/lib/secret-store.ts
#	tests/secret-store.test.ts
@roodboi

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@roodboi

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

Ready for lead: head 207150574f840d8e05c6a3f24399ec76b5bf9335

What this head is: 9fc9e26f, whose second-family review closed with nothing left to change before merge, plus a real merge of next at c96c4441aa (#769 HACK-2172 and #756 HACK-2163), with no rebase and no force-push. The conflicts were small:

Every line #756 and #769 added to the shared files (secret-store.ts, project-env-config.ts, doctor.ts, docs/env.md) is still present, so #756's containment and #769's live-input guard are intact.

Review:

  • The second-family reviewer is clean on the merge resolution at 20715057. Its earlier rounds on this PR (five must-change items, then two, then one PR-description path cleanup) are all closed.
  • The accepted follow-up, outside this PR, is broader native filesystem concurrency and full CLI-subprocess recovery qualification.
  • Codex's code and security reviews were clean at 9fc9e26f. I've requested a re-review of the merge head.
  • computer pr review-status: CLEAR at 207150574f, with 0 unresolved bot threads. next has since moved again, only on files this PR doesn't touch.

Hosted checks at this head:

  • Passed: test shards 1 to 8, docker-e2e, Change scope, test focus, secret-scan, docs site, and both required checks.
  • Skipped by change scope on this run: Config compiler, Candidate core, linux-process-lifetime, runtime-images and Runtime state models.

Local gates at the merge (Bun 1.4.4, after bun install --frozen-lockfile; next adds jsonc-parser):

Behavior changes for the release note are listed in the PR description:

  • an encrypted_file key file that other users can read is refused;
  • --provision-key never replaces a different key;
  • a key inside a git work tree whose git cannot answer is refused.

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 207150574f

ℹ️ 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".

Comment thread src/lib/project-env-config.ts Resolved
…eck (HACK-2169)

A directory or other non-regular entry named like an env layer is an
existing layer runtime refuses; the integrity inspection now discovers it
and reports the failed read instead of skipping it.

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@roodboi

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

Superseded head: the ready note above was for 20715057. Codex then raised a P2 on that head: an overlay that exists only as a non-regular entry, such as a directory, was not discovered. It is fixed in 9e86ed6ac297c4a56ab02cc8ede057fc6e9e2bc5, with a regression test, and the thread is replied to and resolved. The second-family reviewer is checking this head now. I will post a new ready note once it is clean and the Linux jobs are green.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 9e86ed6ac2

ℹ️ 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".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 9e86ed6ac2

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ 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.

@roodboi

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

Ready for lead: head 9e86ed6ac297c4a56ab02cc8ede057fc6e9e2bc5

This replaces the note for 20715057.

What changed since that note: Codex raised a P2 on the merge head. An overlay that existed only as a non-regular entry (for example a directory named hack.env.staging.local.yaml) was not discovered, so the doctor reported success for a layer runtime refuses. Fixed in 9e86ed6a:

  • discovery takes matching layer names of any entry type;
  • the existing runtime-compatible strict read reports such an entry as an error;
  • a regression test covers it and fails without the fix.

The thread is replied to and resolved.

Review:

Hosted checks at this head:

  • Passed: test shards 4 to 8, the test fan-in, docker-e2e, Change scope, test focus, secret-scan, docs site, and the required checks.
  • Skipped by change scope on this run: test shards 1 to 3, Config compiler, Candidate core, linux-process-lifetime, runtime-images and Runtime state models.

Local gates (Bun 1.4.4):

Behavior changes for the release note are listed in the PR description:

  • an encrypted_file key file that other users can read is refused;
  • --provision-key never replaces a different key;
  • a key inside a git work tree whose git cannot answer is refused;
  • the doctor reports anything it cannot inspect as an error.

Accepted follow-ups:

  • broader native filesystem concurrency and full CLI-subprocess recovery qualification;
  • --literal-pathspecs for the project env key's own tracking check.

🤖 Generated with Claude Code

roodboi and others added 2 commits November 13, 2026 12:28
…k after completed repairs (HACK-2169)

With #767's malformed-layer fixture, doctor --migrate-env-config now exits
non-zero: the layer runtime refuses is a failing secret integrity check.
The test keeps #767's intent (completed repairs kept, the plaintext
inventory says it could not inspect names, the malformed file's text is
never printed) and asserts the named check and that no output, notes or
terminal, echoes the file.

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: 680e9ad7c6

ℹ️ 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".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 680e9ad7c6

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ 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.

@roodboi

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

Ready for lead: head 680e9ad7c625f89a4844bb13810621e8eaf2d9fd

This replaces the note for 9e86ed6a.

What changed since that note:

  • e7d6048c is a real merge of next at 0c0fba2617 (including fix(env)!: restrict value exposure and protect plaintext entries (HACK-2166) #767) into 9e86ed6a. There were no conflicts, and nothing was rebased or force-pushed.
  • 680e9ad7 updates fix(env)!: restrict value exposure and protect plaintext entries (HACK-2166) #767's test "doctor keeps completed repairs when the optional plaintext inventory is unreadable", per your decision that a layer the runtime refuses is a doctor failure. The test now:
    • expects doctor --migrate-env-config --no-interactive to exit non-zero;
    • checks the completed repairs are kept on disk: the compose env_file entry is removed and .hack/.gitignore is created;
    • keeps the "Could not inspect plaintext env names" note assertion;
    • captures doctor's stdout and stderr and asserts the secret integrity check names .hack/hack.env.qa.yaml;
    • asserts the malformed file's sentinel appears neither in the notes nor in the terminal output, which covers this PR's finding message too.

Review:

  • The second-family reviewer is clean on the merge and the test change at 680e9ad7.
  • Codex's code review ("Didn't find any major issues") and automatic security review (no issues) are both clean at 680e9ad7c6.
  • computer pr review-status: CLEAR at 680e9ad7c6, 0 unresolved bot threads. The head contains next at 0c0fba2617.

Hosted checks at this head:

  • Passed: both required checks, test shards 4 to 8, the test fan-in, docker-e2e, Change scope, test focus, secret-scan and docs site.
  • Skipped by change scope on this run: test shards 1 to 3, Config compiler, Candidate core, linux-process-lifetime, runtime-images and Runtime state models.

Local gates (Bun 1.4.4, after bun install --frozen-lockfile):

🤖 Generated with Claude Code

@roodboi
roodboi merged commit 573ceec into next Nov 13, 2026
21 checks passed
@roodboi
roodboi deleted the claude/hack-2169-secret-store-integrity branch November 13, 2026 13:27
roodboi added a commit that referenced this pull request Nov 13, 2026
…ent; close threat-model gaps 17 and 18

isKeyFileTrackedByGit (from #766) spawned git with the full environment,
so HACK_ENV_SECRET_KEY and other Hack key material reached the child.
It now passes childEnvironment(), as #756 requires for tool children.
Regression: a fresh process with a recording git first on PATH checks a
key inside a work tree with a sentinel project key set; git sees no Hack
key variable. It fails without the change.

The threat model's gaps 17 (HACK-2169, closed by #766) and 18 (HACK-2165,
closed by #762 and this branch) now describe the implemented guards and
their remaining limits, and the rows that pointed at them as open agree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roodboi added a commit that referenced this pull request Nov 13, 2026
Conflict in docs/security/env-secrets-threat-model.md gaps 16-19 only:
next's wording for gaps 17 (#766) and 19 (#768, HACK-2171) is kept; gap 18
takes next's #762 text plus this branch's mode tightening, read warning,
ACL/umask limits and markerless-work-tree note; gap 16 keeps the doctor
reference, and the two table rows that pointed at gaps 17/18 say
"narrowed". #768's cleanup receipts merge cleanly with this branch's
strictGit live-input collector (its new caller keeps the non-strict path).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roodboi added a commit that referenced this pull request Nov 13, 2026
… where the store is read (HACK-2177) (#779)

Fixes HACK-2177. A machine can have an old global encrypted store
(`~/.hack/secrets.enc.json`) whose key file is gone. Since #766, `hack
doctor` then failed in every project, saying not to provision a new key,
but offering no other way forward.

## What changes
- **`hack secrets retire-store`**: a deliberate, reversible way forward
when the key is gone for good.
- It acts on the store this project selects (`--path` or the current
directory), or the global store outside a project.
- It moves the store into a new directory `<store>.retired-<UTC time>/`
beside it, keeping its name. Under the store's lock:
- it creates that directory exclusively (0700; refused if the name
exists);
    - it hard-links the store into it (a link never replaces a file);
- it renames whatever is at the store path into the same directory under
an unpredictable, checked-free name, and removes that extra name only
when it is the archived file.

A replacement put at the store path meanwhile is kept beside the
archive. Every rename target and the only name ever removed are inside
the new directory, and failure cleanup is `rmdir`, which refuses unless
the directory is empty.
- It only retires a file that holds a Hack encrypted-store envelope (the
configured path is project input), never the key file itself (same
device and inode), and refuses when the config that selects the store
cannot be read or does not parse. It classifies the store again under
the lock, and the prompt names the resolved store path.
- It never deletes, reads or prints an entry, and leaves the key file
alone.
  - It prints what moved and the `mv` command to put it back.
- Without a terminal it is refused with `E_INTERACTIVE_REQUIRED` unless
`--yes` is passed, as `hack secrets delete` is.
- It refuses a store that opens with the key in use, an absent store,
and a store unreadable for another reason (a refused key, an unreadable
or linked store); for those, doctor says what to fix instead.
- It refuses a store that another registered checkout's key file or the
OS keychain still opens. It also refuses, unless `--allow-unchecked` is
passed, when coverage is incomplete: an unreadable registry, an
unavailable checkout, or an unreadable key. `HACK_SECRETS_FILE_KEY`
overrides key files and the keychain at runtime and may be set in
another project's shell, which the command cannot check, so in practice
retiring needs `--allow-unchecked` as the explicit confirmation that
none does. Doctor's recovery text says so.
- **Doctor's `secret integrity` finding** for a store with a missing or
wrong key:
- **Error** only in a project whose runtime reads the store: its legacy
env contract declares a store-backed (`source: "keychain"`) value, or
plaintext is kept in the backend. This is the same condition the runtime
uses before it opens the store; an unreadable contract counts as reading
it. In every other project it is a **warning**, so a machine-wide store
no longer fails unrelated projects.
- It says the store's **entries cannot be counted without the key**. The
envelope is a single encrypted payload, so there is no count to read; I
kept that honest rather than estimating.
- It names the **registered projects that read it** (names only, from
configuration; no store is opened for that). Store files are compared by
device and inode, so a reader reaching the store through a symlinked
directory is found. When the registry or a project's config cannot be
read, it says the list may be incomplete instead of claiming no project
reads it.
- A key file that exists but cannot be read is reported as such (fix its
permissions), not as missing, and is not retirable.
- It gives the two choices: restore the key, or retire the store. "Do
not provision a new key" stays.
- **Retired files** are listed as information (the check stays `ok`),
with an `mv` command using absolute paths. A quoted `~` would not
expand.
- **Recovery workflow**: a new "Choose one" step names both choices
whenever that finding is present.
- **Docs**: the env guide, a short upgrade-guide section "An old global
secret store whose key is gone", and the regenerated CLI reference.

```mermaid
flowchart TD
  D[hack doctor in a project] --> L{global/project store: key opens it?}
  L -- yes --> OK[ok]
  L -- missing or wrong key --> U{does this project's runtime read the store?}
  U -- yes --> E[error + users + two choices]
  U -- no --> W[warning + users + two choices]
  E --> C{choose}
  W --> C
  C -- restore the key --> OK
  C -- hack secrets retire-store --> R[store renamed .retired-UTC, nothing deleted]
  R --> I[doctor: retired file listed as info, check ok]
  I -- mv it back --> L
```

## On the severity rule (c)
I agree with the rule. The predicate is the runtime's own condition for
opening the store, not merely "the configured backend is
encrypted_file". A global backend setting is inherited by every project,
so that would fail them all again.

## Tests (fake homes only)
`tests/secret-store-retire.test.ts` sets HOME, HACK_HOME and the global
config inside a temp root, and uses two registered projects sharing the
global store: `app-a` reads it (a legacy contract with a store-backed
value), and `app-b` does not.
- Store present, key missing: `app-a` gets an error, `app-b` a warning.
The warning names `app-a`, says the entries cannot be counted, gives
both choices, and contains no value or key. The recovery block shows
"Choose one" with both.
- Retire through the real CLI (a subprocess with a private env):
- Non-interactive without `--yes`: `E_INTERACTIVE_REQUIRED`, and the
store is byte-identical.
- Without `--allow-unchecked`: refused, naming `HACK_SECRETS_FILE_KEY`;
the store is byte-identical.
- With `--yes --allow-unchecked`: the store is gone from its name, and
exactly one `.retired-*` file holds identical bytes. The output names
both paths and the `mv` back, and contains no value.
- Doctor afterwards: one `info` finding in both projects, with the
absolute `mv`.
- Restore by moving the file back: the bytes are identical, the warning
is back, and with the key restored the store opens and the synthetic
value reads back. Nothing was lost.
- Refusals: a store that opens, a refused (0644) key, and an absent
store. Nothing moves in any of them.
- `tests/missing-project-error-code.test.ts` pins `secrets retire-store`
as a command that works without a project (it uses the global store).

Local gates (Bun 1.4.4):
- `bun run typecheck` is clean.
- `bun run check`: 43 warnings, the baseline on `next`; none in the
touched files.
- `privacy:check` and gitleaks are clean.
- 256 tests pass across the new file, doctor secret integrity, secret
store, doctor --fix, missing-project and the recovery/CLI suites.
- `bun run docs:check` could not run locally (the docs site's
dependencies are not installed here); the hosted docs job covers it.

No real store, key, keychain or registry was touched on any host.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- codesmith:footer -->
---
<a
href="https://backend.blacksmith.sh/track/run-proof?expires=1797170835&installation_model_id=17053&pr_number=779&ref=codesmith_pr_footer&repository=hack-dance%2Fhack&return_to=https%3A%2F%2Fgithub.com%2Fhack-dance%2Fhack%2Fpull%2F779&variant=proof_this_pr&signature=5ef87b200e768cb167158adc51217589d16c03a6e192b50c422d6cdc77325255"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-proof-this-pr-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-proof-this-pr-light.svg"><img
alt="Proof this PR"
src="https://pr-comments-assets.blacksmith.sh/codesmith/proof-proof-this-pr-dark-3x.png"
width="121" height="28"></picture></a>
<sup><a
href="https://app.blacksmith.sh/hack-dance/codesmith/hack/pr/779?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: Kit <344842+roodboi@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
roodboi added a commit that referenced this pull request Nov 13, 2026
Add next.17 “what changed and what to do” notes to the release summary,
both upgrade guides and the env guide. Users get concrete script
migrations for hidden env values and native followed logs, the next.16
key-backup warning, and recovery guidance for partial cleanup.

These describe merged source changes intended for next.17; they do not
claim a release is published. The docs site remains pinned to next.16.
#764 and #779 remain explicitly pending, and the recorded next.16 trials
keep their original limits.

| Subject | Merged contract checked at base `0a0bfdb8` |
| --- | --- |
| #747: native plain followed logs, streams, timestamps and structured
session results |
[native-log-follow.ts](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/src/backends/native-log-follow.ts),
[followed-service-log
contract](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/docs/reference/native-authored-commands.md#followed-service-logs)
|
| #767: metadata/`has_value`, scoped plaintext reads, terminal-only
reveal, stdin secret writes, `env protect`, global-only lifecycle
hooks/processes and YAML daemon API | [canonical prerelease
wording](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/scripts/prerelease-plan.ts#L158),
[env
command](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/src/commands/env.ts),
[env
routes](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/src/daemon/routes/env.ts),
[env
contract](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/docs/env.md)
|
| #769 / HACK-2172 and #768/#783: protect live keys/config/YAML
including filesystem aliases; cleanup receipts, partial removal and
config-pointer reconciliation |
[project-env-config.ts](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/src/lib/project-env-config.ts),
[Doctor
cleanup](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/src/commands/doctor.ts),
[canonical cleanup
note](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/scripts/prerelease-plan.ts#L164)
|
| #766: Doctor refuses unusable env layers |
[doctor-secret-integrity.ts](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/src/lib/doctor-secret-integrity.ts)
|
| #773: loopback proxy ports, diagnosis without automatic repair |
[proxy-loopback.ts](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/src/lib/proxy-loopback.ts),
[Doctor](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/src/commands/doctor.ts)
|
| #770: inferred route readiness, 240-second grace, HTTP 200–499 limits
and `service_healthy` exclusion |
[http_probe.rs](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/packages/runtime-core/src/provider/http_probe.rs),
[probe
contract](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/docs/guides/native-candidate.md#native-http-readiness-probe)
|
| #778: bridge connection readiness after pool restart |
[bridge.rs](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/packages/runtime-core/src/provider/bridge.rs)
|
| #758 and #776: merged startup fix and newer optional-daemon behavior,
kept separate from next.16 | [project
command](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/src/commands/project.ts),
[Doctor](https://github.com/hack-dance/hack/blob/0a0bfdb8d1ee818d89bb49a2109bd88b0a9fea1b/src/commands/doctor.ts)
|

Validation at `f1d0d72fede5db6ce46f56039ba22a47911aa308`:
- Persistent reviewer: clean after round 2.
- Pinned Bun 1.4.4: docs prepare/check/typecheck and production site
build pass.
- Production-mode HTTP smoke: 95 docs, isolated searches/exports,
redirects/refusals, 37 diagram blocks / 74 SVGs and 183 recipe
links/anchors pass. The rendered upgrade guide returns 200 and contains
the new guidance.
- Privacy check, diff check and full-history Gitleaks pass (1,295
commits, no leaks).
- Source citations and documented command forms checked against the
merged base.

Documentation only: no runtime, workflow, site pin, snapshot or global
client changes. No live migration, keys, daemon, Docker or native VM
tests were run. Lead owns batch integration and deployment.

<!-- codesmith:footer -->
---
<a
href="https://backend.blacksmith.sh/track/run-proof?expires=1797183263&installation_model_id=17053&pr_number=785&ref=codesmith_pr_footer&repository=hack-dance%2Fhack&return_to=https%3A%2F%2Fgithub.com%2Fhack-dance%2Fhack%2Fpull%2F785&variant=run_proof_on_this_pr&signature=b04dd421a08341f70e804e671fd68678f102d1c4152146bdea18adf6de909981"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-run-proof-on-this-pr-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-run-proof-on-this-pr-light.svg"><img
alt="Run Proof on this PR"
src="https://pr-comments-assets.blacksmith.sh/codesmith/proof-run-proof-on-this-pr-dark-3x.png"
width="168" height="28"></picture></a>
<sup><a
href="https://app.blacksmith.sh/hack-dance/codesmith/hack/pr/785?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: Kit <344842+roodboi@users.noreply.github.com>
roodboi added a commit that referenced this pull request Nov 14, 2026
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>
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