feat: neutral herdr hook + deterministic hook-ownership audit#1
Conversation
Add bin/audit-hook-ownership.sh: for every harness (claude/codex/cursor/gemini) it resolves the hook config to the actual command target(s) it runs and classifies each - OWNED (home-manager symlink into nix/repo), UNOWNED (hand-placed regular file), or MISSING - failing CLOSED on any non-owned or missing target. This replaces eyeball-grepping a harness dir (or a transcript log) to guess what a hook runs and who owns it. It currently reports the real broken state: codex/cursor targets MISSING (exit 127), claude target a manual regular file, gemini's workflow-ladder scripts unowned. Fix the fail-open in both existing audits: they hardcoded /Users/jwalinshah/projects/dotfiles, now a 27-byte pointer file, so config-ownership died on a bogus mismatch and doc-freshness scanned zero files and reported ok. Both now resolve the repo from the script's own location (then ~/dotfiles) and fail closed if neither holds home.nix. Adds tests/audit-hook-ownership.test.sh (owned/unowned/missing classification, nested gemini shape, fail-closed exit codes). Deploys the new script via home.nix. Not wired into rebuild.sh yet - it is RED until the hook targets are repaired (that is the bounded proof task).
Replace the three hand-placed per-harness herdr SessionStart scripts with ONE neutral, repo-owned script (bin/herdr-agent-state.sh) that takes --source and --label, so there is no hardcoded harness identity. Point Claude, Codex, and Cursor hooks at it with their own source/label; deploy it via home.nix. This makes audit-hook-ownership green for all three (verified by simulation). - Remove Gemini's workflow-ladder hooks (unowned, hand-placed dead weight) and their deployment + ownership check. - Wire audit-hook-ownership.sh into rebuild.sh (runs after the switch, so the deployed script is present and the audit is green). - audit-doc-freshness: strip code blocks before link scanning and make doc-link drift advisory (was flagging code examples like x["embedder"] as broken links); real stale-token and absolute-path regressions stay fatal. - audit-config-ownership: drop content-matches on runtime-mutated files (.cursor/cli-config.json, .gemini antigravity settings - finding kunchenguid#9); fix two latent set -e/set -u crashes in its advisory existence-check sections (unbound ${user} in the LaunchAgent scan, a failing grep substitution) so the content-ownership verdict is the exit code. - Remove herdr runtime session logs still tracked in the tree (Phase 0's untracking did not land on this branch's HEAD).
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 77715ce4bf
ℹ️ 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".
| local t=$1 real | ||
| if [ -L "$t" ]; then | ||
| if [ ! -e "$t" ]; then echo "MISSING(dangling-symlink)"; return; fi | ||
| real=$(readlink -f "$t" 2>/dev/null || true) |
There was a problem hiding this comment.
Use portable symlink resolution for the audit
On the target macOS setup, the configured PATH only adds /opt/homebrew/bin rather than Homebrew coreutils' gnubin, so readlink is the BSD implementation and does not support -f. In that environment this assignment leaves real empty, causing every Home Manager symlink - including the newly deployed ~/bin/herdr-agent-state.sh - to be classified as UNOWNED(symlink-outside-nix-and-repo), so the new rebuild.sh gate fails even when the hook target is correctly deployed. Use a portable resolver (realpath, Python, or greadlink fallback) here.
Useful? React with 👍 / 👎.
| "hooks": [ | ||
| { | ||
| "command": "bash '/Users/jwalinshah/.codex/herdr-agent-state.sh' session", | ||
| "command": "bash '/Users/jwalinshah/bin/herdr-agent-state.sh' session --source codex --label codex", |
There was a problem hiding this comment.
Refresh Codex hook trust with the new command
Changing this Codex hook command makes the existing trusted hash for /Users/jwalinshah/.codex/hooks.json:session_start:0:0 in home/.codex/config.toml stale. The Codex hooks docs state that non-managed hooks must be trusted before they run and that new or changed hooks are skipped until trusted, so after rebuild this SessionStart hook will warn/skip instead of reporting to herdr until /hooks is approved or the declarative trusted hash is updated.
Useful? React with 👍 / 👎.
See voice-engine-swift/wayfinder/map.md ticket #1. Runtime bootout alone wasn't persistent across rebuilds; this makes it declarative.
Intent
Repair agent-harness hook ownership and give it a deterministic, fail-closed audit — the bounded "proof task" from the machine audit. Every harness's SessionStart hook now runs one neutral, repo-owned script, and a new auditor resolves every hook to its real target and classifies ownership instead of anyone eyeball-grepping a directory (or a transcript log) to guess.
Activation is deferred to a
darwin-rebuild(the reviewer's gate). The hook config files are out-of-store symlinks, so their new content is already live, but~/bin/herdr-agent-state.shdeploys on the rebuild. Inrebuild.shthe switch runs before the audits, so the auditor sees the deployed script and is green.The auditor (the deliverable)
bin/audit-hook-ownership.sh: for each harness (claude/codex/cursor/gemini) it resolves the hook config to the actual command target and classifies it —OWNED(home-manager symlink into nix/repo),UNOWNED(hand-placed regular file), orMISSING— failing closed. Tested (tests/audit-hook-ownership.test.sh, 6 assertions). Pre-fix it reported the real broken state: codex/cursor targetsMISSING(exit 127), claude target a manual file, gemini's workflow-ladder unowned.The repair
bin/herdr-agent-state.sh— takes--source/--label, no hardcoded harness identity — replacing the three hand-placed per-harness copies. Deployed once viahome.nix.OWNED(repo), 0 violations.audit-hook-ownership.shwired intorebuild.shas a green gate.Fixing the fail-open audits (finding kunchenguid#10)
Both audits hardcoded
~/projects/dotfiles(now a 27-byte pointer), soconfig-ownershipdied on a bogus mismatch anddoc-freshnessscanned zero files and reported ok. Both now resolve the repo from their own location and fail closed on a missing root. Fixing that unmasked several latent issues, all handled:doc-freshness: strip code blocks before link-scanning (ax["embedder"]code example was flagged as a broken link) and make doc-link drift advisory; real stale-token and absolute-path regressions stay fatal.config-ownership: drop content-matches on runtime-mutated files (.cursor/cli-config.json, gemini antigravity settings — finding Personalize yangjingping kunchenguid/dotfiles#9, they drift by design); fix twoset -e/set -ucrashes in its advisory existence-check sections (unbound${user}, a failinggrepsubstitution) so the content-ownership verdict is the exit code.Also
.gitignorekeeps new ones out).Verification
nix evalof the darwin config: clean (valid.drv).config-ownershipexit 0,doc-freshnessexit 0, auditor test passes.hook-ownershipis correctly RED pre-rebuild (script not deployed yet) and simulated green post-rebuild.darwin-rebuild switch) is the reviewer's gate; after it, all three audits are green and the three harness hooks report their correct source/label.