Repository navigation
fix(release): refresh-aggregate exits 1 silently on a merged stale aggregation PR #34709
Description
fix(release): refresh-aggregate exits 1 silently on a merged stale aggregation PR
Pre-flight
- Memory recall:
abandon stranded release lane unpublished tag fresh release→ 0 hits — no relevant lessons - Discovery pass: 6 recent commits on target file (latest GH#34686: fix(release): parse gh stderr HTTP status in recovery release probe #34688/afda17a870); 0 open claimed issues; open draft chore(release): aggregate unpublished v3.39.31 recovery #34650 is an old unmerged aggregate, not a code change
- File refs verified: 4 refs checked at
mainccf3073d5c - Tier:
tier:standard. The error-reporting contract is decided; the worker still chooses the message wording and helper placement. - Seeded draft PR decision recorded: skipped. The change is small and the brief carries the full context.
Origin
- Created: 2026-10-15
- Created by: ai-interactive (v3.39.31 release recovery, Ref GH#34600: fix: closed-issue hold re-comments every cycle under App read auth #34603)
- Conversation context: While recovering the stranded v3.39.31 lane,
aidevops release refresh-aggregate 34690exited 1 in about 5 seconds with no output. chore(release): aggregate v3.39.31 recovery after #34682 and #34688 #34690 had already merged, andmainhad then advanced through GH#34687: docs: audit image-library upload reachability #34691 and GH#34685: fix: scan pushed commit ranges in gh wrapper guard #34692.
Reproducer
Observed live on 2026-10-14 with v3.39.31 deployed (setup.sh at b44b336c42):
$ aidevops release refresh-aggregate 34690 # #34690 merged as b44b336c42; main then at c1db70b11d
(no output)
exit 1 after ~5s
Cause: full-loop-release-aggregate-recovery.sh:1667-1670 requires .state == "open" and returns 1 without a message.
What
aidevops release refresh-aggregate <PR> must never fail without a message. Every refusal in _full_loop_release_refresh_aggregate should print one Successor aggregation refused: <reason> line to stderr. A stale aggregation PR that was merged, and whose merge commit is still an ancestor of origin/main, must give a specific diagnostic. That diagnostic names the merge SHA and says to open a successor from that merge commit's trailers. Adopting merged stale PRs as successor input is an optional extension. Implement it only if it reuses the existing manifest helpers without new trust decisions.
Why
Operators and agents get no signal from a bare exit 1. In this session it cost a full investigation cycle, ending in a hand-built successor (#34701), while concurrent merges kept invalidating exact-tip aggregates.
Tier
Selected tier: tier:standard
Tier rationale: This is a known diagnostics pattern inside an already decided fail-closed boundary. The worker chooses wording and whether to extract a helper.
How (Approach)
Files to Modify
EDIT: .agents/scripts/full-loop-release-aggregate-recovery.sh:1642-1732(_full_loop_release_refresh_aggregate). Replace each bare|| return 1that is a user-facing refusal with a stderr diagnostic followed byreturn 1. Model the wording on the existing refusals at lines 1677-1679 (Successor aggregation refused: ...) and_full_loop_successor_complete_manifestat 1546-1554.EDIT: .agents/reference/release-aggregation-recovery.md:6-27. Document the merged-stale case.
Complete Write Surface
- Callers/readers:
full-loop-release-helper.sh:883-893(refresh-aggregatedispatch). This is the only caller (rg -n "_full_loop_release_refresh_aggregate" .agents/scripts). - Writers/mutation paths: inside the function,
release_lane_begin_aggregate_successor, the refs POST, PR creation and lane bind/finish. These are unchanged; only the messages on refusal paths before them change. - Existing verification/tests:
.agents/scripts/tests/test-full-loop-release-aggregate-recovery.sh:1660-1690calls the function with stderr discarded. Existing assertions must still pass. - Schemas/config: none affected. The change adds only
printf ... >&2lines. The lane JSON written byrelease_lane_begin_aggregate_successor(release-lane-helper.sh:596-636) and the PR body format from_full_loop_successor_bodystay byte-identical, and no config key is read or added. - Generated/deployed mirrors:
~/.aidevops/agents/scripts/viasetup.sh. No generated files. - Migrations/backfills: none required. The new messages are emitted only on refusal paths, and no persisted lane, receipt or ref format changes, so existing lanes such as
refs/heads/aidevops/release-laneneed no backfill. - Cleanup/rollback paths: reverting the PR restores the silent exits in
.agents/scripts/full-loop-release-aggregate-recovery.sh. The refusals happen before or instead of the existing writes, so a rollback leaves no orphaned branches, PRs or lane records beyond what the current code already leaves.
Implementation Steps
- At line 1666-1670, read
.state/.merged_atbefore the open-state assertion. If the PR is merged, printSuccessor aggregation refused: #<PR> already merged as <merge_commit_sha>; open a successor from its trailers at the current main tip, then return 1. For other mismatches (closed unmerged, wrong base), print the specific reason. - Add messages to the remaining silent refusals: fetch failure; stale head equals main; missing manifest in the body; lane read failure; branch/tree fence mismatch; PR lookup ambiguity.
- Update the reference doc paragraph.
- Run
shellcheckand the existing recovery test.
Hazards and Compatibility
- Concurrency/atomicity: unchanged. All new output happens before or instead of the existing returns, and no lane write is added.
- Migration/rollback: none. Revert restores the old silent behaviour.
- Mixed-version/backward compatibility: exit codes are unchanged (still 1), so scripts that check only the status are unaffected.
- Idempotency/retry: unchanged.
- Partial failure/recovery: refusals after
release_lane_begin_aggregate_successorkeep the existing retry-adoption semantics. Only add messages there.
Verification Before Dispatch
shellcheck .agents/scripts/full-loop-release-aggregate-recovery.sh
bash .agents/scripts/tests/test-full-loop-release-aggregate-recovery.sh
AIDEVOPS_FULL_LOOP_REPO=marcusquinn/aidevops bash .agents/scripts/full-loop-release-helper.sh refresh-aggregate 34690 # merged PR: expect exit 1 plus a "Successor aggregation refused" line naming b44b336c42- Surface mapping: the shellcheck run and the test cover the changed function. The live command against merged chore(release): aggregate v3.39.31 recovery after #34682 and #34688 #34690 proves the new merged-PR diagnostic and confirms no lane write happens; the merged check runs before
release_lane_read. - Broad verification trigger: not required. One function, diagnostics only.
Files Scope
.agents/scripts/full-loop-release-aggregate-recovery.sh.agents/reference/release-aggregation-recovery.md
Acceptance Criteria
-
refresh-aggregateagainst a merged aggregation PR exits 1 and prints aSuccessor aggregation refusedline that names the merge SHA. - Every
return 1in_full_loop_release_refresh_aggregatethat a user can reach through valid input has a preceding stderr diagnostic. - No exit code, lane write or fence check changes. The existing
test-full-loop-release-aggregate-recovery.shpasses, andshellcheckis clean.
Context & Decisions
- Silent-exit sibling of release: recover-aggregate GitHub release probe fails silently when gh omits --include headers on 404 #34686 (fixed by GH#34686: fix(release): parse gh stderr HTTP status in recovery release probe #34688), in the same recovery subsystem.
- Non-goal: changing the exact-tip requirement. That is tracked in the companion recovery-starvation issue.
aidevops.sh v3.39.31 plugin for OpenCode v1.19.0 with claude-opus-5-5 spent 6h 55m and 411,972 tokens on this with the user in an interactive session.
Activity
vladimirdulov commentedon Oct 15, 2026
DISPATCH_CLAIM nonce=2cbba9343e64224f735562804b9a7fe3 runner=vladimirdulov ts=2026-10-15T00:27:51Z max_age_s=1800 version=3.39.31 opencode_version=1.19.0 lease_token=2cbba9343e64224f735562804b9a7fe3 device=device-1791387943-2519751-22989 session=issue-34709 phase=prelaunch expires_at=1792024193
vladimirdulov commentedon Oct 15, 2026
CLAIM_RELEASED reason=blocked runner=vladimirdulov ts=2026-10-15T00:34:34Z aidevops_version=3.39.31 opencode_version=1.19.0
Terminal blocker: reason=runner_capability_unmet owner=runner-recovery task=34709 attempt=f6a87ed294d725770a1577b6.
Projected state: status:available.
Next action: Release for a capable runner. Recheck declared secret and local-data requirements before claiming; do not impose a global issue hold or publish secret values.
Raw evidence remains in protected worker telemetry.