Driver
Surfaced 2026-05-19 during Rex review of PR #300 (the check-jq-installed.sh SessionStart hook for #280): the new hook uses the legacy onboarding.yaml-only walk-up to find the ops-fork root. That walk works for single-fork adopters (default) and for split-portfolio v1, but silently fails for split-portfolio v2 — under v2, onboarding.yaml lives in the private sibling repo, the public fork is anchored only by .apexyard-fork, and the walk-up condition fails. v2 adopters won't see the banner.
Audit of .claude/settings.json shows this isn't unique to check-jq-installed.sh — every SessionStart entry except link-custom-skills.sh uses the legacy onboarding.yaml-only walk-up shape:
r=$PWD; while [ ! -f "$r/onboarding.yaml" ] && [ "$r" != / ]; do r=${r%/*}; done; exec "$r/.claude/hooks/<name>.sh"
This is legacy v1 behaviour that pre-dates the v2 anchor (.apexyard-fork) added in framework #242. The correct shape is to source _lib-ops-root.sh and call resolve_ops_root, which recognises both anchors (v2 marker first, legacy v1 pair as fallback).
Scope
Sweep every entry in .claude/settings.json under hooks.SessionStart (and any PreToolUse/PostToolUse entries that also do inline walk-up). Replace the inline walk-up shell with:
. "$(dirname "$0")/_lib-ops-root.sh" 2>/dev/null && \
r=$(resolve_ops_root "$PWD") && \
exec "$r/.claude/hooks/<name>.sh"
Or — if the hook itself uses _lib-ops-root.sh internally — drop the inline walk-up entirely and let the hook resolve from $PWD.
Audit the actual hook scripts too: any hook that does its own walk-up for ops-fork detection should source _lib-ops-root.sh instead of inlining the legacy two-file check.
Acceptance Criteria
Out of Scope
- Adding new SessionStart hooks (this is a refactor of existing ones).
- Changing the v1 fallback semantics —
_lib-ops-root.sh already handles it.
- A meta-hook that lints inline walk-up shells. The audit + sweep is one-time; future hooks will be caught in code review against the AgDR.
Risks / Dependencies
- Risk: subtle behavioural change for adopters who somehow relied on the legacy walk-up silently failing on v2. Mitigation: the v2-aware walk-up is a strict superset — v1 conditions still match, plus v2 anchors. No legitimate adopter loses any functionality.
- Risk:
_lib-ops-root.sh itself has a bug that propagates across all SessionStart hooks. Mitigation: it's already in use by link-custom-skills.sh and the merge-gate hooks; well-tested. If anything, this sweep increases the bug surface area for that lib, making bugs in it land faster.
Glossary
| Term |
Definition |
| Split-portfolio v2 |
Framework layout where onboarding.yaml + apexyard.projects.yaml + projects/ live in a private sibling repo (framework #242), with the public fork anchored only by .apexyard-fork |
| Walk-up |
The pattern of walking from $PWD toward / looking for a marker file that identifies the ops-fork root |
_lib-ops-root.sh |
The shared library function resolve_ops_root <start_dir> that recognises both the v2 marker (.apexyard-fork) and the legacy v1 pair (onboarding.yaml + apexyard.projects.yaml) |
| Ops-fork root |
The top of the apexyard fork tree — where session state lives, where hooks resolve overrides from, and where v2's .apexyard-fork marker sits |
Surfaced during Rex review of PR #300 on 2026-05-19.
Driver
Surfaced 2026-05-19 during Rex review of PR #300 (the
check-jq-installed.shSessionStart hook for #280): the new hook uses the legacyonboarding.yaml-only walk-up to find the ops-fork root. That walk works for single-fork adopters (default) and for split-portfolio v1, but silently fails for split-portfolio v2 — under v2,onboarding.yamllives in the private sibling repo, the public fork is anchored only by.apexyard-fork, and the walk-up condition fails. v2 adopters won't see the banner.Audit of
.claude/settings.jsonshows this isn't unique tocheck-jq-installed.sh— every SessionStart entry exceptlink-custom-skills.shuses the legacyonboarding.yaml-only walk-up shape:This is legacy v1 behaviour that pre-dates the v2 anchor (
.apexyard-fork) added in framework #242. The correct shape is to source_lib-ops-root.shand callresolve_ops_root, which recognises both anchors (v2 marker first, legacy v1 pair as fallback).Scope
Sweep every entry in
.claude/settings.jsonunderhooks.SessionStart(and any PreToolUse/PostToolUse entries that also do inline walk-up). Replace the inline walk-up shell with:Or — if the hook itself uses
_lib-ops-root.shinternally — drop the inline walk-up entirely and let the hook resolve from$PWD.Audit the actual hook scripts too: any hook that does its own walk-up for ops-fork detection should source
_lib-ops-root.shinstead of inlining the legacy two-file check.Acceptance Criteria
.claude/settings.jsonresolves the ops-fork root via_lib-ops-root.sh(recognises both.apexyard-forkand the legacy v1 pair)while [ ! -f "$r/onboarding.yaml" ]); replace with_lib-ops-root.shusageOut of Scope
_lib-ops-root.shalready handles it.Risks / Dependencies
_lib-ops-root.shitself has a bug that propagates across all SessionStart hooks. Mitigation: it's already in use bylink-custom-skills.shand the merge-gate hooks; well-tested. If anything, this sweep increases the bug surface area for that lib, making bugs in it land faster.Glossary
onboarding.yaml+apexyard.projects.yaml+projects/live in a private sibling repo (framework #242), with the public fork anchored only by.apexyard-fork$PWDtoward/looking for a marker file that identifies the ops-fork root_lib-ops-root.shresolve_ops_root <start_dir>that recognises both the v2 marker (.apexyard-fork) and the legacy v1 pair (onboarding.yaml + apexyard.projects.yaml).apexyard-forkmarker sitsSurfaced during Rex review of PR #300 on 2026-05-19.