Skip to content

fix(agents): close the SE PR-merge cycle — --auto, self-review fallback, CTO handoff, ci.yml edited trigger#201

Merged
ohld merged 3 commits intoproductionfrom
fix/staff-engineer-merge-race
Apr 25, 2026
Merged

fix(agents): close the SE PR-merge cycle — --auto, self-review fallback, CTO handoff, ci.yml edited trigger#201
ohld merged 3 commits intoproductionfrom
fix/staff-engineer-merge-race

Conversation

@ohld
Copy link
Copy Markdown
Member

@ohld ohld commented Apr 25, 2026

Summary

Two commits, both surfaced from this morning's session investigating why PRs #174, #196, #199, #200 were stuck. Together they close the Staff Engineer PR-merge cycle for the dominant ohld-authored case.

fe65fa3--auto kills the merge/CI race

PR #200 hit "the base branch policy prohibits the merge" 25s after the agent woke, because the bare gh pr merge --squash ran while test (~2 min) was still in flight. The previous 5-iteration polling loop masked this race; #187 removed it and exposed it. Replaced with gh pr merge --squash --auto — GitHub queues the squash and fires the moment lint+test pass. No polling, no race, no --admin bypass. Verification step learns three states (MERGED / queued / real-failure).

1f259c9 — step 7 self-review fallback + CTO handoff + ci.yml edited trigger

Three failure modes upstream of --auto:

  1. Step 7 told SE to never use gh pr comment for the review signal, but gh pr review --approve|--request-changes always exits non-zero with the self-review block when the gh CLI is authed as the PR author — the dominant case for ohld-authored PRs. SE's working fallback (proven across PRs chore(deps): upgrade Python 3.10/3.12 → 3.14 (Batch 0) #193chore(deps): fastapi 0.115 → 0.136, uvicorn 0.32 → 0.46 (Batch 3) #198) is a prefixed gh pr comment "STAFF ENGINEER REVIEW: APPROVED|CHANGES REQUESTED — <summary>". Documented as the expected path; reserved the "real review required" guidance for external-author PRs.

  2. PR test: describe_memes tests + suppress QA firefighting #174 sat 9 days because a gh pr comment change-request was posted with no Paperclip handoff, so CTO never picked it up. Made paperclipCreateIssue [pr:NNN] address review changes MANDATORY whenever changes are requested for internal authors only (CTO can't fix external PRs — those tag @ohld instead).

  3. Step 8a gated on reviewDecision == APPROVED, which never triggers for ohld-authored PRs (self-review block leaves it empty). Re-stated as "Step 7 produced an APPROVAL outcome" — covers both real --approve and the comment-fallback. Branch protection on production requires only lint+test, so --auto queues regardless.

Plus: ci.yml's pull_request triggers gain edited so retargeted PRs fire CI. Today PR #196 retargeted from the deleted main branch to production and notify-staff-engineer fired but lint+test did not.

Step 1 now also fetches headRefName so the internal-vs-external check (used in step 7's CTO handoff scope and step 8b's merge gate) actually has both vars to work with — previously the prose mentioned branch-prefix matching but only author was fetched.

Codex review

Two rounds. Round 1 found 1 [P1] (CTO handoff over-scoped to external authors) + 1 [P1] (changes-requested fallback didn't cancel a previously queued auto-merge). Round 2 found 1 [P2] (auto-merge cancel must come BEFORE the comment, not after — race window) + 1 [P2] (HEAD_BRANCH referenced but never fetched). All four addressed in 1f259c9. Round 3: PASS — zero [P1] findings.

Known follow-ups (Codex round-3 [P2]s, deferred)

These are about fe65fa3's pre-existing design, not introduced by this PR. Worth a follow-up:

  1. gh pr checks red gate is over-broad — step 8c inspects every check on the PR, not just the required lint+test. The legacy linters.yml workflow runs in parallel; if it fails, SE will post "CI red, leaving merge blocked" even though --auto would still merge once required checks pass. Fix: gh pr checks --required or filter to required check names. Optional bigger fix: delete linters.yml (it's superseded by ci.yml per fix(agents): close the PR review cycle — read trigger payload, force --approve, poll CI #189's intent).
  2. Paperclip issue marked done when auto-merge is just queued — if lint or test later turns red and no one pushes again, the queued merge sits indefinitely with no open execution issue to wake another agent. Trade-off: keep blocked until state == MERGED, OR add a periodic "queued PR sweep" routine.

Test plan

  • python3 -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))" — parses; pull_request.types == ['opened', 'reopened', 'synchronize', 'edited']
  • agents/staff-engineer/AGENTS.md re-read end-to-end after edits: no dangling Fail-loud verification references, no verified via reviewDecision == APPROVED above (that block was removed), step 8a now uses "APPROVAL outcome" wording
  • Two rounds of /codex review with model_reasoning_effort=high — final round: zero [P1]
  • lint + test jobs green on this PR
  • Post-merge: SE should pick up the next PR with the new flow on the next sync (24h) — no immediate runtime impact since the prompt only takes effect after agents/_sync_config.py propagates to Paperclip

Safety

  • No production code touched — diff is YAML config + Markdown prompt
  • No DB migrations, no docker-compose changes, no env vars
  • ci.yml edited is purely additive — worst case is more CI runs on retargets
  • AGENTS.md change is a runtime prompt — affects only future SE wakeups after sync; no instant impact
  • Backout: git revert <sha> reverts both commits cleanly

PR #200 hit "base branch policy prohibits the merge" 25s after the agent
woke, because the bare `gh pr merge --squash` ran while `test` (~2min)
was still in flight. The agent comment incorrectly diagnosed it as a
self-approval block and asked ohld to merge manually.

Branch protection on `production` does NOT require pull-request reviews
(only `lint` and `test` status checks), so self-approval was never the
issue. The actual problem was the merge-vs-CI race; the previous
5-iteration polling loop masked it but was removed in #187, exposing it.

Replace the polling loop + bare merge with `gh pr merge --squash --auto`.
GitHub now queues the squash-merge and fires it the moment required
status checks (`lint`, `test`) pass. No polling, no race, no admin
bypass needed.

Verification step learns three states:
- MERGED: CI was green when --auto ran; immediate merge → done
- OPEN + autoMergeRequest != null: queued, GitHub will fire → done
- OPEN + autoMergeRequest == null: real failure → blocked

Single-shot fast-fail on already-red CI stays (avoids stale "queued"
issues when there's nothing left to wait for). Explicitly forbids
--admin in the merge instructions to remove the bad runtime suggestion
that appeared on PR #200.
@ohld
Copy link
Copy Markdown
Member Author

ohld commented Apr 25, 2026

⚠️ Staff Engineer review — changes needed (cannot request via GH because we're the same auth identity)

GitHub blocks self-review with --request-changes (gh auth status shows the agent is authenticated as ohld, same as PR author). Posting findings inline; please treat this as CHANGES_REQUESTED for the merge gate.

Hard blocker: allow_auto_merge: false on the repo

The fix relies on `gh pr merge --squash --auto`, but auto-merge is disabled at the repository level on `ffmemes/ff-backend`:

```
$ gh api repos/ffmemes/ff-backend --jq '{auto_merge: .allow_auto_merge}'
{"auto_merge":false}
```

When `allow_auto_merge` is false, `gh pr merge --auto` returns "Pull request Auto merge is not allowed for this repository". It does NOT fall back to immediate merge. So the new instructions will land in the verify-step `else` branch (`state=OPEN, autoMergeRequest=null`) and mark every internal PR `blocked`. That's strictly worse than the polling loop it replaces.

The PR's own test plan ("Land this PR... auto-merge will fire as soon as lint+test pass — that itself validates the fix on the very next agent run") is impossible without enabling auto-merge first.

Required fix — one of

A. Enable auto-merge on the repo first (recommended, single API call):
```bash
gh api -X PATCH repos/ffmemes/ff-backend -f allow_auto_merge=true
```
Then this PR's instruction change is correct as written. Suggest adding a one-line note in AGENTS.md naming the prerequisite (`allow_auto_merge: true` at repo level), and ideally a first-run guard in the agent: `gh api repos/ffmemes/ff-backend --jq .allow_auto_merge` → comment + bail if false.

B. Drop `--auto` entirely — replace the polling loop with a longer wait (5-min Monitor poll on required checks) that bails on red CI. Less elegant than `--auto` but no repo-setting prerequisite. Not recommended.

Strong preference for A. `--auto` is the right primitive; the repo setting is the only thing missing.

Verified-OK in the diff

  • Internal-author classification (section 8b) untouched. ✅
  • Three-state verification matrix (MERGED / queued / failed) is the right shape. ✅
  • Fast-fail-on-red `jq 'any(...)'` correctly handles empty-checks array. ✅
  • Explicit `--admin` guardrail. ✅
  • Section 8a (review approval requirement) preserved. ✅

Out of scope but worth noting

  • Conflict edge case: `--auto` queues even on a conflicting PR; the merge never fires and the agent reports `done`. Low-frequency in practice (ohld eyeballs), not a blocker — but a future tweak: check `gh pr view --json mergeable -q .mergeable` before queueing.

Next step

Run `gh api -X PATCH repos/ffmemes/ff-backend -f allow_auto_merge=true` (you have admin), push the AGENTS.md `allow_auto_merge: true` prerequisite note as a follow-up commit on this branch, and the PR will be re-reviewable. CI is currently green (`lint`, `test`, `notify-staff-engineer` all pass) so no other blockers.

Leaving the Paperclip execution issue `done` per agent protocol; ohld will pick up the next push.

…ited trigger

Three failure modes from this morning's investigation, all upstream of the
--auto fix in this PR's first commit:

1. Step 7 told SE to never use `gh pr comment` for review signal, but
   `gh pr review --approve|--request-changes` always exits non-zero with
   the self-review block when the gh CLI is authed as the PR author —
   the dominant case for ohld-authored internal PRs. SE's working
   fallback (proven across PRs #193, #194, #195, #197, #198) is a
   prefixed `gh pr comment "STAFF ENGINEER REVIEW: APPROVED|CHANGES
   REQUESTED — <summary>"`. Document it as the expected path; reserve
   the "real review required" guidance for external-author PRs.

2. PR #174 sat 9 days because a `gh pr comment` with "Verdict: changes
   requested" was posted with no Paperclip handoff, so CTO never picked
   it up. Make `paperclipCreateIssue [pr:NNN] address review changes`
   MANDATORY whenever changes are requested (either path).

3. Step 8a gated on `reviewDecision == APPROVED`, which never triggers
   for ohld-authored PRs (self-review block leaves it empty). Re-state
   as "Step 7 produced an APPROVAL outcome" — covers both real `--approve`
   and the comment-fallback. Branch protection on `production` requires
   only `lint+test`, not a formal review, so `--auto` queues regardless.

Plus: ci.yml `pull_request` triggers gain `edited` so retargeted PRs
fire CI. Today PR #196 retargeted from the deleted `main` branch to
`production` and `notify-staff-engineer` fired but `lint+test` did not.

Net diff in AGENTS.md: +15/-9 lines. Step 8 (the --auto rewrite from
the first commit) is unchanged. Followed Anthropic's Opus 4.7 prompting
guidance: positive examples over negation, scope-explicit, no nested
"but if" branching.
@ohld ohld changed the title fix(agents): use gh pr merge --auto to kill the Staff Engineer CI race fix(agents): close the SE PR-merge cycle — --auto, self-review fallback, CTO handoff, ci.yml edited trigger Apr 25, 2026
@ohld
Copy link
Copy Markdown
Member Author

ohld commented Apr 25, 2026

STAFF ENGINEER REVIEW: CHANGES REQUESTED

(Posted as comment because gh pr review --request-changes self-blocks on ohld-authored PRs. Branch protection requires only lint/test, so reviewDecision being empty does not block merges.)


P1 — Blocking. --auto will fail because allow_auto_merge: false on this repo.

I verified against the repo settings:

$ gh api repos/ffmemes/ff-backend --jq '.allow_auto_merge'
false

gh pr merge --squash --auto errors out when auto-merge is disabled at the repo level. It does not fall back to a normal merge. The new step 8 verification then sees `state=OPEN, autoMergeRequest=null` and routes to the "Real failure" branch: posts the `⚠️ Merge did not queue` comment, marks the Paperclip issue `blocked`. Net effect: every internal PR ends up blocked instead of merged. PR #200's symptom (manual merge required) becomes the steady state.

Fix is one line, not in this PR — flip the repo setting first:

```
gh api -X PATCH repos/ffmemes/ff-backend -f allow_auto_merge=true
```

Then this PR ships as-is and the race fix actually works.

P2 — Non-blocking but real. Queued-merge `done` can strand PRs on later CI failure.

In step 8 verification, `OPEN + autoMergeRequest != null` marks the Paperclip issue `done`. If `lint` or `test` later fails after the queue is set, GitHub leaves the PR open and the queue never fires. No synchronize event = no agent re-trigger. The PR sits forever until ohld notices.

Mitigations to consider: keep the issue `in_progress` (blocked on a separate poller routine), or at minimum call this risk out in a CTO follow-up so the next reader knows.

P3 — Minor. Bundled CI workflow change isn't justified in PR body.

`.github/workflows/ci.yml` adds `types: [opened, reopened, synchronize, edited]`. The PR description doesn't mention it. Not dangerous (just runs CI on description edits), but it's an unrelated bundled change. Either justify in the body or split into its own commit.


Recommendation: enable `allow_auto_merge` on the repo first, push a small follow-up commit (or amended push) clarifying the `edited` trigger, then this lands cleanly. I'll re-review on the next `synchronize`.

@ohld for the manual repo-setting flip.

@ohld ohld closed this Apr 25, 2026
@ohld ohld reopened this Apr 25, 2026
@ohld
Copy link
Copy Markdown
Member Author

ohld commented Apr 25, 2026

STAFF ENGINEER REVIEW: CHANGES REQUESTED — P1 verified by /codex round 4 + direct gh api check.

P1 — allow_auto_merge: false breaks the new step 8 happy path

Confirmed via gh api repos/ffmemes/ff-backend --jq .allow_auto_mergefalse.

gh pr merge --squash --auto will fail at command time on this repo, the new verification block falls into the OPEN/autoMergeRequest == null branch, and every internal PR gets marked blocked. The whole point of --auto (kill the merge/CI race) only works if the GitHub repo has auto-merge enabled. It doesn't here.

Fix (pick one):

  1. Enable repo auto-mergegh api -X PATCH repos/ffmemes/ff-backend -f allow_auto_merge=true. Single command, then the prompt works exactly as written. Recommend this.

  2. Amend AGENTS.md to fall back when gh pr merge --squash --auto exits non-zero: try bare gh pr merge --squash. CI is already green by step 8c so the original race is gone — bare merge is safe at that point.

Codex round 4 also flagged (worth folding into the same fix):

  • [P2] Step 8b classification uses headRefName alone, so a fork PR named fix/FFM-foo matches the internal allowlist and could auto-merge. Replace with gh pr view --json isCrossRepository,headRepositoryOwner and treat fork PRs as external. (PR fix(agents): close the SE PR-merge cycle — --auto, self-review fallback, CTO handoff, ci.yml edited trigger #201 itself is in-repo so unaffected.)

  • [P2] (already noted in PR description as deferred) Marking the Paperclip issue done when auto-merge is queued strands the PR if CI later goes red. Either keep blocked until state == MERGED or add a sweep routine.

Holding the merge. CI is green (lint+test+notify-staff-engineer all pass), so once P1 is addressed I'll re-approve and merge on the next sync.

Disabled auto-merge defensively (no queue existed). Will re-review on next synchronize.

…external

Address PR #201 SE review (P1 + P2):

P1 — repo had `allow_auto_merge: false`, so `gh pr merge --squash --auto`
would fail and route every internal PR to `blocked`. Repo setting flipped to
`true` out-of-band (`gh api -X PATCH repos/ffmemes/ff-backend -f
allow_auto_merge=true`); AGENTS.md step 8 now names the prerequisite and
tells the agent to bail with a clear comment + `blocked` if it ever drifts
back to false. No bare `gh pr merge` fallback — that re-opens the CI race
PR #201 exists to close.

P2 — step 1 META fetch now also pulls `isCrossRepository` as `IS_FORK`.
Step 7 (CTO handoff scope) and step 8b (merge gate) gate the internal
classification on `IS_FORK == false` AND existing author/branch logic. A
fork PR named `fix/FFM-foo` previously matched the internal allowlist via
branch-prefix alone and could have auto-merged.

PR #201 itself is `isCrossRepository=false`, so unaffected. Pure prompt
edit; no production code touched.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@ohld
Copy link
Copy Markdown
Member Author

ohld commented Apr 25, 2026

Addressed P1 + P2 in 39ca518:

P1 — allow_auto_merge: false blocker. Flipped the repo setting out-of-band:

$ gh api -X PATCH repos/ffmemes/ff-backend -f allow_auto_merge=true
{"auto_merge":true}

Verified: gh api repos/ffmemes/ff-backend --jq .allow_auto_mergetrue. So the original --auto happy path now works as written.

To prevent silent regressions if the setting drifts back to false, AGENTS.md step 8 now documents the prerequisite explicitly and tells the agent to:

  1. Verify gh api repos/ffmemes/ff-backend --jq .allow_auto_merge before queueing.
  2. If false: comment a clear remediation on the PR, mark the Paperclip issue blocked, exit.
  3. Never fall back to a bare gh pr merge --squash — that re-opens the CI race this whole PR exists to close.

I considered option B (bare-merge fallback) and rejected it: step 8c only filters FAILURE/ERROR/CANCELLED, so an in-flight PENDING check would let a bare merge race against CI. Step 8c → bare merge is not safe in general, only safe if all required checks have already turned SUCCESS. Adding that conditional logic adds complexity for a misconfig that's now a one-API-call fix.

P2 — fork-PR spoofing. Step 1 META fetch now also pulls isCrossRepository as IS_FORK. Step 7's CTO handoff scope and step 8b's merge gate now gate the internal classification on IS_FORK == false AND (existing AUTHOR/HEAD_BRANCH logic). A fork PR named fix/FFM-foo would have previously matched the internal allowlist via branch-prefix alone — now it's correctly classified as external (tag @ohld, no merge).

P2 (deferred — queued-merge done strands PRs on later red CI): intentionally not addressed in this PR. Already documented as a known follow-up in the PR description; needs a separate sweep routine or MERGED-gating change. Out of scope for the immediate review-blocking issues.

Re-review on the next synchronize.

@ohld
Copy link
Copy Markdown
Member Author

ohld commented Apr 25, 2026

STAFF ENGINEER REVIEW: APPROVED — main merge-race fix is sound (--auto, allow_auto_merge precheck, isCrossRepository fork detection, self-review-block comment fallback, behaviour matrix for verify-merge). Step 1 captures AUTHOR/HEAD_BRANCH/IS_FORK and step 8 reuses them — clean.

/review (Claude structural) + /codex (adversarial second-opinion) both passed. Codex flagged two non-blocking follow-ups, filing the [P1] as a separate ticket:

  1. [P1] edited asymmetry — ci.yml now triggers on edited (covers base-branch retargets) but .github/workflows/staff-engineer-trigger.yml still only fires on opened/reopened/synchronize. A retargeted PR onto production would run CI without firing the SE wake. Real gap, separate fix.
  2. [P2] Queued-merge issue closureautoMergeRequest != null → Paperclip done relies on a future synchronize push to re-trigger if CI goes red mid-queue. PR documents this tradeoff explicitly. Accepting.

Skipped /cso — the ci.yml change is a single event-type addition, no scripts/secrets/auth surface.

Queuing --auto merge.

@ohld ohld merged commit 8e6eca6 into production Apr 25, 2026
3 checks passed
ohld added a commit that referenced this pull request Apr 25, 2026
…symmetry (#202)

Mirrors PR #201's ci.yml change so that retargeting a PR onto production
re-fires the Staff Engineer review wake. Without this, a retargeted PR
could be merged without Staff Engineer review.

Fixes FFM-717.

Co-authored-by: Paperclip <noreply@paperclip.ing>
Copy link
Copy Markdown
Member Author

ohld commented Apr 26, 2026

Fix verification — post-#201 internal PRs

Window: 2026-04-25T20:44Z (merge of #201) → 2026-04-26T18:10Z (~24h)
Sample: 3 internal PRs to production, all author ohld

PR Branch SE approval comment Merged CI gap (review→merge) Verdict
#202 fix/staff-engineer-edited-trigger 2026-04-25T20:53:32Z — "STAFF ENGINEER REVIEW: APPROVED … Queueing auto-merge." 20:53:48Z 16s (CI already green) ✅ Working
#203 chore/comms-prompt-snapshot 2026-04-26T09:45:30Z — "STAFF ENGINEER REVIEW: APPROVED" 09:45:47Z 17s (CI already green) ✅ Working
#204 fix/ru-channel-exclude-videos 2026-04-26T18:07:45Z — "STAFF ENGINEER REVIEW: APPROVED … PR Quality Score: 10/10" 18:07:57Z 12s (CI already green) ✅ Working

Notes:

Aggregate verdict: fix verified — the --auto flag is closing the CI race across all sampled PRs.


Generated by Claude Code

ohld added a commit that referenced this pull request Apr 27, 2026
…firefighting (#205)

* feat(agents/se): Self-Check Gate + Anti-Patterns Log to close silent firefighting

Adds a mandatory verification step before staff-engineer marks an execution
issue done. Each PR-review outcome (merged / queued / blocked-CI / changes-
requested / external / already-resolved) maps to a path with explicit checks
that must pass; failures route to status=blocked with a reason instead of a
silent close.

Path A3 probes the next-link (Coolify deploy) via last_online_at on
/api/v1/applications/<uuid>: if the container is still healthy on a pre-merge
timestamp 5 min after merge, files [chain-broken:coolify-not-triggered]
HIGH for CTO. This catches the GH→Coolify webhook drop case ohld reported.

ANTI-PATTERNS.md is the case-log feeding the gate. Each row maps to a check;
six seed rows from real PRs (#177 6-day silent trigger drop, #199 17h
zero-artifact merge, #201 auto-merge race during changes-requested, #200
bare-merge CI race, the user-reported chain-break, and the
SC=$(gh pr view --json comments) JSON corruption found while testing).

Tested locally against PRs #199 (correctly identified as silent exit, 0
review-signal artifacts) and #201 (signal found, A3 timestamp probe passes).

* fix(agents/se): address codex review of self-check gate

Two real bugs codex caught before push:

[P1] A2/B2/C2 grep was too narrow — only matched the comment-fallback form
(STAFF ENGINEER REVIEW: APPROVED) used when GitHub self-review-blocks ohld.
For non-ohld internal and external authors, step 7 posts a real `gh pr review
--approve -b "Review summary"` whose body lacks the prefix. Those valid
approvals would have failed the gate. Fix: accept EITHER a .reviews[] entry
with state="APPROVED" OR the comment-prefix. Same dual-form for D1
CHANGES_REQUESTED.

[P2] A3 Coolify probe fired immediately after merge, when last_online_at is
still pre-merge from the previous deploy. Coolify needs ~3-5 min for the
deploy + healthcheck cycle. Without a grace window, every healthy PR would
file [chain-broken:coolify-not-triggered] and drown CTO in false alarms.
Fix: probe only fires when now - mergedAt >= 300s; otherwise deferred to
QA's hourly Process Health Check.

Both fixes logged as #7 and #8 in ANTI-PATTERNS.md so the same blind spots
don't reappear in future gate revisions.

* fix(agents/se): address SE agent CHANGES REQUESTED on PR #205

Two P1s the SE agent caught reviewing this PR:

[P1.1] Path A3 used BSD `date -u -j -f` which doesn't exist on the Linux
agent runtime. Probe failed at the first line, MERGED_EPOCH was empty, the
chain-broken issue never fired. Fix: GNU `date -u -d` auto-parses both
mergedAt (ISO 8601) and last_online_at (YYYY-MM-DD HH:MM:SS).

[P1.2] A2/B2/C2/D1/E1 jq commands grepped ALL artifacts on the PR. Spec
said "for THIS run" but had no time filter, so a stale APPROVED comment
from a prior wake would let a current silent-exit wake pass A2 — exactly
the silent-close mode the gate was meant to fix. Fix: capture
WAKE_START_ISO at the top of every wake, filter via --arg t in A2 + D1
jq calls.

Skipped per minimal-code preference: ANTI-PATTERNS rows for these (caught
pre-merge, not a production failure), Coolify UUID drift note, D3 MCP-tool
prose tweak. All non-blocking from the SE review.

Verified locally against PR #205 own data — the new wake-scope filter
correctly finds the SE review when WAKE_START < submittedAt and rejects
it otherwise.

* fix(agents/se): route all SE wake exits through Self-Check Gate

Codex adversarial review of PR #205 (5 findings, 1 P1 + 4 P2):

[P1] Steps 0/7/8 bypassed the gate entirely — `paperclipUpdateIssue
done|blocked` was called directly inside each step, so the verification
block in step 9 was dead code as wired (cases #1/#3/#4/#5 from the
ANTI-PATTERNS log all closed via those direct calls). Restructured so
each terminal branch sets `OUTCOME_PATH=A|B|C|D|E|F` and jumps to step
9; step 9 is now the single `paperclipUpdateIssue` call site for the
wake. Added an explicit terminal-status mapping table.

[P2] `/tmp/sc.json` and `/tmp/app.json` are cross-run race traps —
two parallel SE wakes overwrite each other's snapshots. PR-scoped to
`/tmp/sc-${PR_NUMBER}.json` and `/tmp/app-${PR_NUMBER}.json`.

[P2] Path A3 Coolify probe didn't validate the curl response. Empty
body on 401/404/500/network failure → `jq -r .last_online_at` empty →
`date -u -d ""` undefined → silent no-op. Now checks curl exit, HTTP
200, and non-empty `last_online_at`; on failure files
`[chain-broken:coolify-probe-unhealthy]` for CTO.

[P2] Path E was missing the `>= $WAKE_START_ISO` freshness filter that
A2/B2/C2/D1 already have. A stale APPROVED review from a prior wake
could have let a current silent-exit external-PR wake pass the gate.
E1/E2 now filter by wake-start.

[P2] A3 chain-broken contradicted the general "any failed check →
blocked" rule. Called out A3 as the explicit non-blocking exception:
SE delivered review + merge regardless; the broken handoff is a
separate `[chain-broken:*]` ticket. Updated "When a check fails" and
the terminal-status table to make this explicit.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(agents/se): round-2 review fixes — bash semantics, precheck order

Round-2 SE review of commit 72ea6ed found 2 P1 regressions and 1 P2
that the structural pass missed:

[P1] CI-red branch fall-through. Old code had `exit 0`; my refactor
replaced it with `# ... goto step 9`, but bash doesn't honor prose
comments — execution fell through to `gh pr merge --squash --auto`
and queued the merge for a PR that should stay blocked. Fix: introduce
a `SKIP_MERGE` flag set by either precheck failure (CI red OR
auto-merge disabled), and gate the `gh pr merge ...` block on
`[ -z "$SKIP_MERGE" ]`. The single exit point is still step 9.

[P1] A3 chain-broken issue never filed. Both A3 failure branches used
`: "file [chain-broken:*] PR #<n> ..."`, but `:` is the bash null
command — the string is just an evaluated argument, no issue is ever
created. The wake closed `done` silently exactly as ANTI-PATTERNS #5
warned about. Fix: bash block now computes `A3_RESULT` and `A3_DETAIL`
only; an explicit prose step below the bash block tells the agent to
invoke the `paperclipCreateIssue` MCP tool when A3_RESULT is
probe-unhealthy or not-triggered (filing an issue is a tool call, not
a shell command, so it shouldn't have been in the bash block).

[P2] `allow_auto_merge` precheck was documented AFTER the
`gh pr merge --squash --auto` call that depends on it. If the setting
drifts back to false, the merge call errors first and the diagnostic
recovery never runs. Moved the precheck above the merge command (now
under the same `c.` heading as the CI-red precheck), gated by the
SKIP_MERGE flag described above.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
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