Skip to content

cold-store-reaper: nightly-backup status step spuriously FAILED when status prints >20 lines — head closes the pipe, SIGPIPE + pipefail fail a healthy run #1430

Closed

Description

@dezren39

Bug

scripts/nightly-backup.sh's status step can mark the whole nightly run FAILED even when cold-store status succeeds — a race between head closing the pipe and the producer finishing its writes.

The step (nightly-backup.sh, status block):

if "${COLD_STORE}" status | head -n 20; then

The script runs with set -uo pipefail, so the pipeline's exit status is the producer's when the producer is the failing element. When status output exceeds 20 lines, head exits after line 20 and closes the read end; a producer still writing gets SIGPIPE (a shell producer exits 141; the real cold-store is Python, which converts SIGPIPE into BrokenPipeError at flush and exits non-zero). Whether the producer finishes first depends on output size and scheduling — so the step flips between ok and FAILED on identical healthy state.

Reproduction (verified this run, main 37418e56)

Using the script's own documented test seams (COLD_STORE_BIN, EXPORT_TURSO_BIN) with a stub cold-store whose status prints 100 lines and always exits 0, and a stub export-turso that succeeds:

  • run 1: step status: ok, exit 0
  • run 2: step status: ok, exit 0
  • run 3: step status: FAILED, script exit 1, FAILED steps: status — every step actually succeeded

The existing tests in tests/test_nightly_backup.py cannot catch this: their stub status prints exactly one line (stub status summary: objects 0 cold), so head never closes early.

Why it matters

The nightly backup is the cold store's core ritual, and PR #1373 just reworked this script specifically so failures stay loud but not contagious. This bug does the opposite: a spurious status failure makes a fully healthy night exit 1 and report FAILED steps: status, training the operator to ignore the run's verdict — and the failure probability grows every time cold-store status output grows past 20 lines (it is described in the script header itself as a summary shown via head, i.e. truncation is expected).

Acceptance criteria

  • The status step reports ok whenever cold-store status itself exits 0, regardless of how many lines it prints (verify with a stub printing several hundred lines, run repeatedly — e.g. 20 consecutive runs, zero spurious failures).
  • A genuinely failing cold-store status (non-zero exit) still marks the step FAILED and the script still exits non-zero at the end.
  • The printed summary is still truncated for display (~20 lines) — the fix must not dump unbounded status output into the nightly log.
  • tests/test_nightly_backup.py gains a case whose stub status prints more than 20 lines and asserts step status: ok + exit 0, alongside the existing isolation cases.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    app/cold-store-reaperPackage/app: cold-store-reaper (pkgs/ or apps/ in this repo)
    on Oct 27, 2026
  2. developing-today commented on Oct 27, 2026

    @developing-today
    Contributor
  3. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    Filed this run by the new-issue-creator (lane A). Reproduced on main 37418e56 via the script's own COLD_STORE_BIN/EXPORT_TURSO_BIN test seams: a stub cold-store status printing 100 lines and exiting 0 made the run report step status: FAILED and exit 1 in 1 of 3 runs (head -n 20 closes the pipe; SIGPIPE + pipefail). The existing nightly tests use a one-line stub status, so they cannot see it.

    • Session / ID: cron new-issue-creator-code-a run 2026-10-27 10:05 CDT
    • Found via: filed this run
    • Task: filed the issue; queued a small-fix packet
    • Expectation: effort/s — done when the status step's verdict depends only on cold-store status's own exit code, display truncation retained, and a >20-line stub-status regression test exists
    • Links: packet enqueued to repo-health-code-pilot this run
  4. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    Queue task 1434 progress — fix pushed.

    What changed (scope-limited to the status step + regression test):

    • pkgs/cold-store-reaper/scripts/nightly-backup.sh: status step no longer pipes cold-store status directly into head -n 20 under set -o pipefail. It captures full output to a mktemp file, records cold-store's own exit code, then head -n 20 the file purely for display. Verdict depends only on cold-store's exit code; FAILED line now includes the exit code, matching run_step style.
    • pkgs/cold-store-reaper/tests/test_nightly_backup.py: new test_long_status_output_still_ok_and_truncated with a 300-line stub status asserting exit 0, step status: ok, lines 1–20 shown, lines 21/300 absent.

    Test results (laptop, repo conventions):

    • python3 -m unittest pkgs/cold-store-reaper/tests/test_nightly_backup.py -v: 4/4 OK
    • 20 consecutive runs with 300-line stub status (exit 0): 0 spurious failures, display exactly 20 lines
    • Forced failing status (STUB_FAIL_STEP=status): step status: FAILED (exit 3), script exit 1, display still truncated

    PR: #1774 (references #1430, not merged).

  5. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    🤖 Dispatcher binding · openchamber-dispatcher run 2026-10-28 13:45 CDT (burn-by-expiry run: Drew directive 13:44 CDT — fill free session slots, burn the Go window resetting 2:26 PM and the Meta window resetting 4:23 PM)

  6. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · bound

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/cold-store-reaperPackage/app: cold-store-reaper (pkgs/ or apps/ in this repo)kind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)queue/queuedHas a queued task in the Supabase agent queuesource/creatorFiled by the new-issue-creator jobstatus:donetask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions