Skip to content

feat: neutral herdr hook + deterministic hook-ownership audit#1

Merged
jwalin-shah merged 2 commits into
mainfrom
feat/hook-ownership-audit
Jul 10, 2026
Merged

feat: neutral herdr hook + deterministic hook-ownership audit#1
jwalin-shah merged 2 commits into
mainfrom
feat/hook-ownership-audit

Conversation

@jwalin-shah

Copy link
Copy Markdown
Owner

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.sh deploys on the rebuild. In rebuild.sh the 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), or MISSING — failing closed. Tested (tests/audit-hook-ownership.test.sh, 6 assertions). Pre-fix it reported the real broken state: codex/cursor targets MISSING (exit 127), claude target a manual file, gemini's workflow-ladder unowned.

The repair

  • One neutral script bin/herdr-agent-state.sh — takes --source/--label, no hardcoded harness identity — replacing the three hand-placed per-harness copies. Deployed once via home.nix.
  • Claude / Codex / Cursor hooks now call it with their own source+label. Simulated post-rebuild state: all three OWNED(repo), 0 violations.
  • Gemini's workflow-ladder hooks removed (unowned, hand-placed dead weight) — config, deployment, and ownership check.
  • audit-hook-ownership.sh wired into rebuild.sh as a green gate.

Fixing the fail-open audits (finding kunchenguid#10)

Both audits hardcoded ~/projects/dotfiles (now a 27-byte pointer), so config-ownership died on a bogus mismatch and doc-freshness scanned 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 (a x["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 two set -e/set -u crashes in its advisory existence-check sections (unbound ${user}, a failing grep substitution) so the content-ownership verdict is the exit code.

Also

  • Removes herdr runtime session logs still tracked in the tree (an earlier untracking didn't land on HEAD; the recursive .gitignore keeps new ones out).

Verification

  • nix eval of the darwin config: clean (valid .drv).
  • config-ownership exit 0, doc-freshness exit 0, auditor test passes.
  • hook-ownership is correctly RED pre-rebuild (script not deployed yet) and simulated green post-rebuild.
  • Post-merge activation (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.

Jwalin Shah added 2 commits July 10, 2026 09:45
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).
@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

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.

@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: 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment thread home/.codex/hooks.json
"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",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@jwalin-shah
jwalin-shah merged commit 57b76ca into main Jul 10, 2026
@jwalin-shah
jwalin-shah deleted the feat/hook-ownership-audit branch July 10, 2026 17:50
jwalin-shah pushed a commit that referenced this pull request Jul 21, 2026
See voice-engine-swift/wayfinder/map.md ticket #1. Runtime bootout alone
wasn't persistent across rebuilds; this makes it declarative.
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