Skip to content

fix(report-failure): warn on a failed row append instead of aborting - #947

Merged
max-sixty merged 2 commits into
mainfrom
hourly/review-31510773013
Aug 11, 2026
Merged

fix(report-failure): warn on a failed row append instead of aborting#947
max-sixty merged 2 commits into
mainfrom
hourly/review-31510773013

Conversation

@tend-agent

Copy link
Copy Markdown
Collaborator

Found by review-reviewers analysing max-sixty/worktrunk (run 31510773013). Evidence log: https://gist.github.com/a88c03f4d0c3fb1791060ff3dd97d1c4

What happened

Worktrunk's Claude subscription hit its weekly limit during the window, and two runs failed with You've hit your weekly limit · resets 2am (UTC) (visible in each session JSONL, zero tokens billed on the first):

Run Workflow Agent outcome Recorded on the tracker?
31504411090 tend-mention claude -p exit 1 yes — filed worktrunk#3800
31505266914 tend-review on #3791 claude -p exit 1 no

The quota exhaustion itself isn't a tend defect. What the second run exposed is: Report failure ran, hit a GitHub 502, and exited 1 instead of degrading, so the row was never appended. #3800 still reads updated_at: 2026-08-11T15:00:53Z with zero comments and a single row — it reports one stranded run when there were two, and the run it omits is the one whose review of #3791's new HEAD never happened.

Log evidence for the attribution

From run 31505266914's Report failure step (13.8 s, ending in failure):

2026-08-11T15:09:23.2761440Z non-200 OK status code: 502 Bad Gateway body: "<!DOCTYPE html>...
2026-08-11T15:09:23.3052745Z ##[error]Process completed with exit code 1.
2026-08-11T15:09:23.3059103Z ##[end-action id=__max-sixty_tend.__run_13;outcome=failure;conclusion=failure;duration_ms=13816]

The three earlier gh calls in the script are each ruled out, which leaves the append:

  • run_issue_ensure_label is 2>/dev/null || true, so it can neither emit that stderr nor abort.
  • run_issue_canonical is read through if ! EXISTING=$(...), whose failure path prints ::warning::Could not read this repo's tend-outage issues... and exits 0. No ::warning:: appears anywhere in the run log, so the read succeeded.
  • $EXISTING was therefore #3800, and control reached the bare gh issue comment — the only unguarded write left.

Root cause

report-failure.sh guards its read and leaves its append bare:

if ! EXISTING=$(run_issue_canonical "$LABEL" open "$TITLE"); then
  echo "::warning::Could not read this repo's ${LABEL} issues, ..."
  exit 0
fi

if [ -n "$EXISTING" ]; then
  printf '%s\n' "$ROW" | gh issue comment "$EXISTING" -F -   # <- aborts under `set -e`

rate-limit-preflight.sh, the sibling caller of the same lib/run-issue.sh, already guards the identical call — added in e5f0f9b, whose comment reasons about exactly this:

Left bare it would abort here under set -e, costing the run the annotation below, which is worth more than the row: the issue already exists, so the annotation can still name what to close, while the row is one line of evidence among the rows the other refusals appended.

That argument transfers verbatim; report-failure.sh was simply never given the same treatment. This is the common write path, not a corner: once a tracker is open, every later failure in the same incident appends through it — a previous outage cluster put 8 rows on worktrunk#3780 this way, all through this one call.

The fix

Wrap the append, warn, let the step end clean. Deliberately not symmetric — the create branch keeps its abort, because test_report_failure_propagates_a_failed_create already fixes that policy and the reasoning still holds: with no tracker open, a failed create leaves no record of the outage anywhere, so reddening the step is the only surviving signal. An append has a tracker that already carries the incident. The new test's docstring names the asymmetry so it doesn't get "tidied" later.

test_report_failure_survives_a_failed_append_to_the_open_tracker reproduces the production failure: without the change it fails with returncode 1 and the row dropped; with it, exit 0 plus the warning. Full file passes (41 tests).

Gate assessment

  • Gate 1 — confidence: High, acted on. One production occurrence this window, but not a fresh judgement call: e5f0f9b is the project's already-accepted ruling that this exact mechanism on this exact call is a defect, applied to one of the two call sites. The remaining site has now fired. Failure is structural — given a 5xx on the append, the abort is deterministic, not a model behaviour that might go differently on a replay.
  • Gate 2 — magnitude: targeted fix, normal bar. One if wrapper plus a warning line, mirroring an existing guard byte-for-byte. It removes an inconsistency between two callers rather than introducing new policy.
  • Dedup. #859 (persistent failures appending too many rows) and #809 (matrix-leg dedup) both work the opposite axis — how many rows to write, not what happens when a write fails. #857 widens which step failures report at all. None touch the abort. No open PR modifies this file.

Not addressed here

The stranded tend-review on worktrunk#3791 has no retry path — the run failed before stamping the commit, and nothing re-fires until the next push, so that HEAD stays unreviewed. #816 raised both halves of this ("nothing re-runs the trigger it names… leaves the PR silently un-reviewed forever") and was closed COMPLETED on 2026-08-07; the naming half did ship, via the nightly enricher. On this evidence the re-run half looks still live, but that's one observation, so it goes in the evidence log to accumulate rather than reopening anything here.

The two do compound, which is worth flagging: the tracker is the list a maintainer would re-run from, so a dropped row makes a stranded run correspondingly harder to find. That's the argument for this one-line guard, not for widening the PR.

The append to an open `tend-outage` tracker was bare, so a transient 5xx
from `gh issue comment` aborted the step under `set -e` and lost the row.
The read above already degrades to a warning, and the rate-limit caller
guards its equivalent write for the same reason; this call site was the
one left bare.

The create branch keeps the opposite policy on purpose — with no tracker
open a failed create leaves no record of the outage at all, so it still
has to redden the step.
@tend-agent tend-agent added the claude-behavior Behavioral issues found by review-reviewers label Aug 11, 2026

@tend-agent tend-agent left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard itself is right and the test pins it: reverting just the shell change makes test_report_failure_survives_a_failed_append_to_the_open_tracker fail with returncode 1 and the row dropped, and the full file passes (41) with it. One inaccuracy in the new comment block — the closing cross-reference describes rate-limit-preflight.sh as drawing the same fatal/non-fatal line between its two writes, but that caller guards both: its create is wrapped in if ! PAUSE=$(... | run_issue_create_and_reconcile ...), and the comment there says so explicitly ("set -e would take the script down on a failed create and the annotation below — this run's only trace — would never be reached, which is the outcome this path exists to avoid"). The asymmetry in this file is real, but it isn't mirrored there, so a reader who follows the pointer finds the opposite policy on the create.

Comment thread shared/steps/report-failure.sh Outdated
…ard comment

The comment claimed the rate-limit caller draws the same fatal/non-fatal
line between its two writes. It does not — it guards both, because a failed
create there must still reach the annotation naming what to close. Here the
create is the last statement, so the red step is all that is left.
@tend-agent
tend-agent force-pushed the hourly/review-31510773013 branch from b3c88f4 to 437c748 Compare August 11, 2026 16:59
@max-sixty
max-sixty merged commit f3f9b44 into main Aug 11, 2026
9 checks passed
@max-sixty
max-sixty deleted the hourly/review-31510773013 branch August 11, 2026 23:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude-behavior Behavioral issues found by review-reviewers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants