fix(#559): resolve review-marker home pin-first in Rex + /approve-merge#564
Conversation
The merge gate resolves the marker dir via _lib-ops-root.sh::resolve_ops_root (pin-first), but the code-reviewer agent and /approve-merge skill resolved it with a plain walk-up. In split-portfolio mode that walk-up lands on the private portfolio sibling (it has onboarding.yaml + apexyard.projects.yaml) where _lib-review-markers.sh doesn't exist — so markers were written to the wrong dir under a bare (unqualified) name and the gate couldn't see them, blocking every managed-project merge until hand-relocated. - code-reviewer.md + approve-merge/SKILL.md now read the session pin first (~/.claude/apexyard/ops-root-<session>), validate + self-heal a stale pin, and fall back to walk-up only when no valid pin exists — matching the gate. - Verified from a workspace clone: resolves MARKER_HOME to the real ops fork and produces the repo-qualified marker path the gate expects. Follow-up to #230 (which fixed the gate's read path; this fixes the writers). Refs #559
atlas-apex
left a comment
There was a problem hiding this comment.
Code Review: PR #564
Commit: ebf96d56fe761148e229f0da55d86f185e1604ff
Summary
Switches the review-marker home resolution in the code-reviewer agent (Rex) and the /approve-merge skill from a plain filesystem walk-up to a pin-first strategy that mirrors the merge gate's _lib-ops-root.sh::resolve_ops_root. This is the write-side companion to #230 (which fixed only the gate's read path). The fix matters in split-portfolio mode: the ops fork carries only .apexyard-fork while onboarding.yaml+apexyard.projects.yaml live in the private portfolio sibling — so a walk-up from a workspace/<project>/ clone climbs to the sibling (legacy anchor satisfied, but no _lib-review-markers.sh), and markers land where the gate can't see them. Both writers now read the session pin first and self-heal a stale pin before falling back to walk-up, so writer and reader always agree.
Checklist Results
- Architecture & Design: Pass — both writers now use the gate's strategy; single source of truth preserved (
resolve_ops_rootremains canonical, the inline blocks are faithful copies because agent/skill markdown can'tsourcea lib at instruction-read time). - Code Quality: Pass —
IFS= read -r, defensive2>/dev/null || pwd, escape-hatch parity (APEXYARD_OPS_DISABLE_PIN,APEXYARD_OPS_PIN_DIR, unset session id). - Testing: Pass — verified by differential execution against the real lib (see below).
- Security: Pass — no secrets, no leaks; portfolio sibling not named with private project identifiers.
- Performance: N/A — doc-only instruction change.
- PR Description & Glossary: Pass — Glossary present (6 terms), Summary bullets narrative, Refs #559.
- Summary Bullet Narrative: Pass.
- Technical Decisions (AgDR): N/A — implements an already-recorded decision (AgDR-0060 + #381/#230 strategy); no new technical choice introduced.
- Adopter Handbooks: N/A — no handbooks loaded (diff touches only
.mdinstruction files; noarchitecture//general/handbooks present, no language-extension match).
Verification performed
I ran the PR's inline block as a real shell test and diffed its output against the canonical resolve_ops_root from _lib-ops-root.sh across the scenarios that matter:
| Scenario | Inline block | Canonical resolve_ops_root |
Match |
|---|---|---|---|
| Valid pin, cwd = ops fork | ops fork | ops fork | yes |
Valid pin, cwd = /tmp (Rex's real failure scenario) |
ops fork | ops fork | yes |
APEXYARD_OPS_DISABLE_PIN=1 (walk-up fallback) |
ops fork | ops fork | yes |
CLAUDE_CODE_SESSION_ID unset (walk-up fallback) |
ops fork | ops fork | yes |
| Stale pin → self-heal to walk-up | ops fork | ops fork | yes |
I also confirmed the new validation predicate is an exact De Morgan negation of _ops_root_anchor_valid across all four anchor combinations:
| Anchor state at pinned dir | Inline predicate | _ops_root_anchor_valid |
|---|---|---|
.apexyard-fork only |
KEEP | KEEP |
legacy pair only (onboarding.yaml+apexyard.projects.yaml) |
KEEP | KEEP |
onboarding.yaml only (incomplete pair) |
DROP | DROP |
| nothing | DROP | DROP |
Confirmed the live environment reproduces the bug substrate: the ops fork has .apexyard-fork but NOT the legacy pair; the portfolio sibling (../apexyard-portfolio) has the legacy pair but NOT _lib-review-markers.sh. The pin for this session correctly points at the real ops fork. markdownlint-cli2 → 0 errors on both files. Secrets scan clean.
Self-test of the fix end-to-end: writing THIS review's marker resolved MARKER_HOME to the real ops fork and produced the repo-qualified me2resh__apexyard__564-rex.approved the gate expects.
Issues Found
None.
Suggestions
- nit (non-blocking): the inline blocks duplicate
resolve_ops_root's logic by necessity (markdown instructions can'tsourcethe lib at read time). The two inline copies and the lib are now three places that must stay in lock-step. Consider adding a tiny CI check that asserts the inline blocks remain behaviourally equivalent toresolve_ops_root(e.g. extract+exec under fixtures) so a future edit to the lib doesn't silently drift the instructions. Not blocking — the current copies are faithful.
Verdict
APPROVED
🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: ebf96d56fe761148e229f0da55d86f185e1604ff
atlas-apex
left a comment
There was a problem hiding this comment.
Code Review: PR #564
Commit: ebf96d56fe761148e229f0da55d86f185e1604ff
Summary
Switches the review-marker home resolution in the code-reviewer agent (Rex) and the /approve-merge skill from a plain filesystem walk-up to a pin-first strategy that mirrors the merge gate's _lib-ops-root.sh::resolve_ops_root. This is the write-side companion to #230 (which fixed only the gate's read path). The fix matters in split-portfolio mode: the ops fork carries only .apexyard-fork while onboarding.yaml+apexyard.projects.yaml live in the private portfolio sibling — so a walk-up from a workspace/<project>/ clone climbs to the sibling (legacy anchor satisfied, but no _lib-review-markers.sh), and markers land where the gate can't see them. Both writers now read the session pin first and self-heal a stale pin before falling back to walk-up, so writer and reader always agree.
Checklist Results
- Architecture & Design: Pass — both writers now use the gate's strategy; single source of truth preserved (
resolve_ops_rootremains canonical, the inline blocks are faithful copies because agent/skill markdown can'tsourcea lib at instruction-read time). - Code Quality: Pass —
IFS= read -r, defensive2>/dev/null || pwd, escape-hatch parity (APEXYARD_OPS_DISABLE_PIN,APEXYARD_OPS_PIN_DIR, unset session id). - Testing: Pass — verified by differential execution against the real lib (see below).
- Security: Pass — no secrets, no leaks; portfolio sibling not named with private project identifiers.
- Performance: N/A — doc-only instruction change.
- PR Description & Glossary: Pass — Glossary present (6 terms), Summary bullets narrative, Refs #559.
- Summary Bullet Narrative: Pass.
- Technical Decisions (AgDR): N/A — implements an already-recorded decision (AgDR-0060 + #381/#230 strategy); no new technical choice introduced.
- Adopter Handbooks: N/A — no handbooks loaded (diff touches only
.mdinstruction files; noarchitecture//general/handbooks present, no language-extension match).
Verification performed
I ran the PR's inline block as a real shell test and diffed its output against the canonical resolve_ops_root from _lib-ops-root.sh across the scenarios that matter:
| Scenario | Inline block | Canonical resolve_ops_root |
Match |
|---|---|---|---|
| Valid pin, cwd = ops fork | ops fork | ops fork | yes |
Valid pin, cwd = /tmp (Rex's real failure scenario) |
ops fork | ops fork | yes |
APEXYARD_OPS_DISABLE_PIN=1 (walk-up fallback) |
ops fork | ops fork | yes |
CLAUDE_CODE_SESSION_ID unset (walk-up fallback) |
ops fork | ops fork | yes |
| Stale pin → self-heal to walk-up | ops fork | ops fork | yes |
I also confirmed the new validation predicate is an exact De Morgan negation of _ops_root_anchor_valid across all four anchor combinations:
| Anchor state at pinned dir | Inline predicate | _ops_root_anchor_valid |
|---|---|---|
.apexyard-fork only |
KEEP | KEEP |
legacy pair only (onboarding.yaml+apexyard.projects.yaml) |
KEEP | KEEP |
onboarding.yaml only (incomplete pair) |
DROP | DROP |
| nothing | DROP | DROP |
Confirmed the live environment reproduces the bug substrate: the ops fork has .apexyard-fork but NOT the legacy pair; the portfolio sibling (../apexyard-portfolio) has the legacy pair but NOT _lib-review-markers.sh. The pin for this session correctly points at the real ops fork. markdownlint-cli2 → 0 errors on both files. Secrets scan clean.
Self-test of the fix end-to-end: writing THIS review's marker resolved MARKER_HOME to the real ops fork and produced the repo-qualified me2resh__apexyard__564-rex.approved the gate expects.
Issues Found
None.
Suggestions
- nit (non-blocking): the inline blocks duplicate
resolve_ops_root's logic by necessity (markdown instructions can'tsourcethe lib at read time). The two inline copies and the lib are now three places that must stay in lock-step. Consider adding a tiny CI check that asserts the inline blocks remain behaviourally equivalent toresolve_ops_root(e.g. extract+exec under fixtures) so a future edit to the lib doesn't silently drift the instructions. Not blocking — the current copies are faithful.
Verdict
APPROVED
🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: ebf96d56fe761148e229f0da55d86f185e1604ff
Summary
_lib-ops-root.sh::resolve_ops_root, but the code-reviewer agent and/approve-mergeskill used a plain filesystem walk-up. From aworkspace/<project>/clone that walk-up climbs to the private portfolio sibling (it hasonboarding.yaml+apexyard.projects.yaml) — where_lib-review-markers.shdoesn't exist — so markers were written to the wrong directory under a bare, unqualified name the gate can't see.~/.claude/apexyard/ops-root-<session>, validate + self-heal a stale pin, fall back to walk-up only when no valid pin exists) — exactly matching the gate's resolution, so writer and reader always agree.Testing
workspace/<project>/clone (the failure scenario): resolvesMARKER_HOMEto the real ops fork,_lib-review-markers.shis present, andreview_marker_pathyields the repo-qualified<owner>__<repo>__<pr>-rex.approvedthe gate expects. Pre-fix, the same cwd resolved to the portfolio sibling + bare name.markdownlint-cli2on both files → 0 errors.Refs #559
Glossary
.claude/hooks + the.apexyard-forkpin anchor.~/.claude/apexyard/ops-root-<session>written at SessionStart; points at the real ops fork regardless of cwd.resolve_ops_rootalready uses; this PR brings the marker writers in line..claude/session/reviews/<owner>__<repo>__<pr>-<role>.approvedrecording a Rex/CEO approval at a HEAD SHA.