fix(#424): hook walker reads session pin for managed project workspaces#425
Conversation
The settings.json inline walker now checks the session pin file (~/.claude/apexyard/ops-root-<SESSION_ID>) before walking up the directory tree. This resolves hooks from managed project workspaces (workspace/<name>/) where the ops repo is a sibling, not an ancestor. Previously: walker found the portfolio root (has onboarding.yaml but no .claude/hooks/) and silently exited. All hooks — including auto-code-review.sh — never fired from workspace CWDs. Now: pin-first resolution finds the ops root regardless of CWD. Walk-up fallback also requires .claude/hooks/ dir alongside the marker file, so even without a pin, it skips the portfolio root. Single-fork mode: unaffected (ops root has both markers + hooks). Refs #424 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
atlas-apex
left a comment
There was a problem hiding this comment.
Code Review: PR #425
Commit: 1723f2af45eac5eda63b1fef9a23404bc7a7cd87
Summary
This PR fixes the hook walker in .claude/settings.json so that hooks fire correctly when the CWD is a managed project workspace (workspace/<name>/) rather than the ops fork root. All 47 inline walker instances now read the session pin file (~/.claude/apexyard/ops-root-<SESSION_ID>) before falling back to the walk-up. The walk-up itself is hardened to require .claude/hooks/ alongside the marker file, preventing false matches on the portfolio root (which has onboarding.yaml but no hooks directory).
Checklist Results
- N/A Architecture & Design: Config-only change; no domain/application/infrastructure code
- PASS Code Quality: Single walker template applied uniformly across all 47 entries; JSON validates cleanly
- N/A Testing: No unit-testable code changed (the walker is an inline shell one-liner in JSON; the underlying
pin-ops-root.shand_lib-ops-root.share pre-existing and unchanged) - PASS Security: No secrets, no auth changes, no injection vectors
- PASS Performance: Pin read is a single file read (fast path); walk-up is the same cost as before (fallback only)
- PASS PR Description & Glossary: Clear summary with narrative bullets, glossary present with Session pin / Walker / Split-portfolio definitions
- PASS Summary Bullet Narrative: All three bullets explain what changed and why
- N/A Technical Decisions (AgDR): Bug fix only — no new libraries, frameworks, patterns, or architecture changes. The pin mechanism (
pin-ops-root.sh,_lib-ops-root.sh) already exists on the base branch; this PR wires the inline walker to use it.
Issues Found
None.
Detailed Analysis
Walker logic verification — the new walker template is:
r="";
if [ -n "${CLAUDE_CODE_SESSION_ID:-}" ]; then
p="${APEXYARD_OPS_PIN_DIR:-$HOME/.claude/apexyard}/ops-root-${CLAUDE_CODE_SESSION_ID}";
[ -f "$p" ] && IFS= read -r r < "$p" && [ -d "$r/.claude/hooks" ] || r="";
fi;
if [ -z "$r" ]; then
r=$PWD;
while [ -n "$r" ] && [ "$r" != / ]; do
{ [ -f "$r/.apexyard-fork" ] || [ -f "$r/onboarding.yaml" ]; } && [ -d "$r/.claude/hooks" ] && break;
r=${r%/*};
done;
fi;
[ -d "$r/.claude/hooks" ] || exit 0;
exec "$r/.claude/hooks/<hook>.sh"
Scenarios verified:
| Scenario | Pin present | Result |
|---|---|---|
| Single-fork, CWD = ops root | Yes | Pin resolves, hook fires |
| Single-fork, CWD = ops root | No | Walk-up finds markers + .claude/hooks/, hook fires |
| Single-fork, CWD = subdirectory | Either | Both paths work |
Split-portfolio, CWD = workspace/<name>/ |
Yes | Pin resolves to ops root, hook fires (THE FIX) |
Split-portfolio, CWD = workspace/<name>/ |
No | Walk-up climbs above workspace, never finds matching dir, exit 0 (graceful, same as before) |
Portfolio root (has onboarding.yaml, no .claude/hooks/) |
Either | Walk-up requires .claude/hooks/ dir, skips portfolio root (THE HARDENING) |
| Pin file exists but points to stale/invalid path | Yes | .claude/hooks/ check fails, r resets to "", falls through to walk-up |
| No markers anywhere | No | Walk-up exhausts, final guard exit 0 |
JSON validity: Confirmed via python3 -m json.tool.
Walker uniformity: All 47 bash -c commands use the identical template — only the hook filename at the end differs.
Walker count: 47 on this PR, 47 on upstream/dev (the base branch). No hooks added or removed — pure walker-pattern replacement.
Pin format compatibility: The walker reads IFS= read -r r < "$p", matching pin-ops-root.sh's write format printf '%s\n' "$ops_root". Space-safe.
No circular dependency: pin-ops-root.sh itself is the first hook in the SessionStart chain. The walker finds the hook via pin (on subsequent calls) or walk-up (on first call of the session before pin exists). The hook writes the pin using resolve_ops_root_walk (the pure walk-up variant), avoiding self-referential pin lookup.
Suggestions
None — this is a clean, mechanical bug fix with the right safety properties.
Verdict
APPROVED
Reviewed by Rex (Code Reviewer Agent)
Reviewed commit: 1723f2af45eac5eda63b1fef9a23404bc7a7cd87
The settings.json inline walker now checks the session pin file (~/.claude/apexyard/ops-root-<SESSION_ID>) before walking up the directory tree. This resolves hooks from managed project workspaces (workspace/<name>/) where the ops repo is a sibling, not an ancestor. Previously: walker found the portfolio root (has onboarding.yaml but no .claude/hooks/) and silently exited. All hooks — including auto-code-review.sh — never fired from workspace CWDs. Now: pin-first resolution finds the ops root regardless of CWD. Walk-up fallback also requires .claude/hooks/ dir alongside the marker file, so even without a pin, it skips the portfolio root. Single-fork mode: unaffected (ops root has both markers + hooks). Refs #424 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
~/.claude/apexyard/ops-root-<SESSION_ID>(written bypin-ops-root.shat session start) before walking up the directory tree. This resolves hooks fromworkspace/<name>/CWDs where the ops repo is a sibling, not an ancestor..claude/hooks/dir alongside the marker file, so it skips the portfolio root (hasonboarding.yamlbut no hooks) even without a pin.Testing
workspace/yumyum/CWD: hooks now fire via pin resolutionpin-ops-root.shruns): walk-up fallback works for ops repo CWD, silently skips for portfolio CWD (same as before — no regression)Glossary
Refs #424
🤖 Generated with Claude Code