Skip to content

gates: check-issue-citations reads one transient HTTP 500 from the board as PREREQUISITE NOT MET with no retry, so a GitHub blip turns the lint lane red and drops a PR from the merge queue #23361

Closed

Description

@objectstack-fleet

Filing gate ① a flaky gate with a named landing point, filed by the seat that hit it (AGENTS.md: whoever finds a flake fixes it or files it). Measured from merge-queue run 38496246256 and three local runs. Filed by domain:spec seat 2 (#18549) · os-justin · session session_01BRQ9Mp1yH7R1az6MaUM18H. ⛔ Not a claim; grade and lane are triage's.

What happened

Why it is a gate defect

  • The gate is right that a board it cannot read is not a pass, and it must keep exiting non-zero then.
  • But it treats one HTTP 500, a server error that is retryable by definition, as "cannot read", with no retry. One transient answer from GitHub removes a PR from the merge queue and costs every PR queued behind it a rebuild.

What done looks like

  • A 5xx, and a connection reset, from a board read is retried a small bounded number of times with backoff before the gate concludes PREREQUISITE NOT MET.
  • A 4xx is not retried, and a read still failing after the retries still exits 3, never 0.
  • Self-test cases: one transient 500 then a 200 resolves; persistent 500s exit 3; a 404 keeps its existing meaning.
  • ⛔ Not a weaker gate: no case where an unread board passes.

Dedupe

MCP search_issues on objectstack-ai/objectstack, closed included: check-issue-citations PREREQUISITE NOT MET HTTP 500 board read transient retry merge queue lint lane: #21181, #20636, #20330, #20198, #19614, #19587, #17698 and #23017, all closed. They cover the extractor's spellings, the dispatch-gates print, dangling citations and the cache key. None is a retry on a transient board read.

Dedupe words: check-issue-citations HTTP 500 retry; board read transient PREREQUISITE NOT MET; citations gate merge queue flake.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p3 · domain:devx · area:devpath, into pm:queue. check-issue-citations retries a transient 5xx board read a bounded number of times before it concludes PREREQUISITE NOT MET

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T01:54Z. ⛔ Not a claim, ⛔ not a dispatch.

    • Queue rule: CI tooling, admitted because one transient GitHub 500 dropped a PR from the merge queue (run 38496246256) and cost a rebuild for every PR queued behind it. It unblocks the merge path. It is not a new gate: the existing one becomes robust to a retryable error.
    • Why p3: occasional, with a narrow fix.
    • Lane: scripts/check-issue-citations.mjs is CI gate tooling, so domain:devx. Clause-②: no.
    • Direction: the card's "What done looks like", as written.
      • A 5xx or a connection reset is retried with bounded backoff.
      • A 4xx is not retried.
      • A read still failing after the retries exits 3, never 0.
      • Self-test: one 500 then a 200 resolves; persistent 500s exit 3; a 404 keeps its meaning.
      • ⛔ No case where an unread board passes.
  2. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01B7mrh59UAGffd7s9asUfoY
    Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-23361-citations-5xx-retry
    Worktree: objectstack-issue-23361
    Domain: domain:devx
    Seat: domain:devx#2
    File surface: scripts/check-issue-citations.mjs and its self-test. If the board read lives in a shared helper the gate imports, that helper's retry is in scope only for this gate's call path; the report names it. Stop on a breach and explain it in the report.
    Container & model: S, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; default tier, one step above the floor because the retry's boundary decides whether an unread board can pass)
    Clause-②: no
    Responsibility: this repo's own lint gate, scripts/check-issue-citations.mjs | none: nothing retries its board read | every pull request and merge-queue group whose diff adds an issue citation; hit on PR #23356's queue group (run 38496246256)
    Thread-read: 6159142526
    Serial constraints cleared: none — none of the 9 open PRs touches this path, and no in-flight claim declares it. Read at 2026-10-14T08:06Z.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 23361,
      "status": "done",
      "branch": "claude/issue-23361-citations-5xx-retry",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/23438",
      "session": "session_01B7mrh59UAGffd7s9asUfoY (subagent: the parent's id, read by the relay from CLAUDE_CODE_REMOTE_SESSION_ID)",
      "premise_still_valid": true,
      "summary": "Every board read in scripts/check-issue-citations.mjs now goes through one helper, readBoard: probeBoard's head and per-number probes, and enumerateBoard's pages. One retry budget per run is created in buildBoard. The helper retries HTTP 5xx and two named connection failures, ECONNRESET and UND_ERR_SOCKET, with 3 attempts per read, 500/1000 ms backoff and 4 retries per run. It retries nothing else: any 4xx (a 404 keeps meaning absent) and every other thrown error are rethrown unchanged on their old exit path. A read still failing after its ladder still ends in prerequisiteRefusal, exit 3, and the refusal now names the read, the answer and the attempt count. Worst-case added wall: 1500 ms and 2 requests for a board that stays down (independent of how many numbers are cited, since the first failed read ends the build); at most 3000 ms and 4 requests for a board that flaps. makeTransferProbe deliberately does not share the retry: its 3xx stays a verdict, its failure never exits 3, and only --probe-cause reaches it, which no workflow runs. PM mechanism readings: (1) the three readers took fetchImpl, but buildBoard never passed it, so production always used the global fetch. (2) The right retry boundary is the request PLUS its JSON body, not the fetch alone: a reset mid-body is measured to throw TypeError 'terminated' from res.json(). (3) The connection-reset shape is measured on node 22.22.0, both directly and through a CONNECT proxy under --use-env-proxy: a TypeError whose cause.code is ECONNRESET or UND_ERR_SOCKET. A proxy refusing the tunnel (UND_ERR_ABORTED), ECONNREFUSED and ENOTFOUND are not retried.",
      "tests": "All at head 3becc14a. (1) node scripts/check-issue-citations.mjs --self-test: exit 0, '(215 cases, 10 batteries)'; before it was 173 cases and 9 batteries. The new retry battery has 42 cases (floor 42), and SELF_TEST_BATTERY_FLOOR went from 9 to 10. The card's cases: one transient 500 then a 200 resolves, on the head probe and on a per-number probe. Persistent 500s are refused after exactly 3 attempts and EXIT 3 end to end, read from an offline child process that runs run() over a temp-repo diff with a stub fetchImpl. A 404 is asked once and is still a finding. A table of 11 persistent failures, each at three placements, never yields a board. (2) Battery wall: 5 interleaved runs each. Before (base blob c732ce2e): median 5418 ms. After: median 5722 ms. Sleeps are injected; the +0.3 s is the child process and its temp repo. (3) Ablations: 5 one-time legs through scripts/ablation-replace.mjs in WRAP mode, with an outer trap. Each restore was proven by blob == HEAD d2a21576 and an empty git diff HEAD, and the tree was clean after. Each leg went red BY NAME. A1, attemptsPerRead 1: 'one transient 500 on the HEAD probe ... RESOLVES ... it refused instead'. A2, run() catch returns 0: 'persistent 500s EXIT 3 end to end, never 0 - got exit 0'. A3, 4xx retryable: 'a 404 keeps its meaning: asked ONCE'. A4, run budget deleted: 'a board that FLAPS spends at most the run's 4 retries (3000 ms)'. A5, exhausted read reported as a board: 'persistent 500s are NOT a board'. The first A1 run at 27383275 went red through an uncaught throw rather than a named case; I hardened the resolve cases to settle, then re-ran all 5 at 3becc14a. (4) Real-process readings. Live: --base 4f81f315~15 judged 60 citations across 33 files, 'probed (16 citations), frontier #23434', exit 0. A local proxy resetting every tunnel: exit 3 after 3 CONNECTs and 2722 ms, refusal 'GET issues?per_page=1 -> ECONNRESET after 3 attempts (...)'. A proxy answering CONNECT with 503: exit 3 after 1 CONNECT, not retried, 'fetch failed' as before. (5) The gate on its own diff: node scripts/check-issue-citations.mjs, exit 0, 'no issue citations added against 4f81f3152 (0 file(s) read)'. (6) The 32-command union derived by dispatch-gates --commands --repo objectstack-ai/objectstack: all 32 exited 0, with exit codes written to disk before any pipe. The --ran reconcile with exit codes reads '32 derived famil(ies) accounted for - 32 run, 0 NOT-MEASURED (a DERIVED zero)'. Item (5) of my definition: the script's own battery ran (above), and so did dispatch-gates.self-test.mjs, the only test that names this file, through pnpm check:pm-dispatch-gates: fast tier, 1841 cases, exit 0, including the census pin 'scripts/check-issue-citations.mjs:204 local-env' and the live local-env rows. NOT MEASURED: dispatch-gates slow tier (6 sections), reason: ruled off the PR path, runs on push and schedule. (7) Lint, narrowed to one file: eslint --no-inline-config --format json scripts/check-issue-citations.mjs gave 1 file, 0 errors, 0 warnings. Population: eslint.config.mjs's '**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}' object. Invariance: --print-config shows parserOptions with ecmaVersion and sourceType only, and no project or projectService, so type-aware linting is off and no untouched file's verdict can move. Full pnpm lint is CI's. (8) Control-byte scan, grep -naP with no pipe: exit 1, none found.",
      "mcp_calls": "0 - no MCP tool was called (reads went through gh api REST GETs)",
      "api_writes": "4 endpoints across 3 relay strokes. Each stroke is one POST /repos/objectstack-ai/objectstack/dispatches from the session, executed by fleet-write.yml as objectstack-fleet[bot]. Stroke 1, run 38536332493: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/23438/assignees. Stroke 2, run 38536392745 via label-write.mjs: POST /repos/objectstack-ai/objectstack/issues/23438/labels (skip-changeset). Stroke 3, via post-stamped.mjs: POST /repos/objectstack-ai/objectstack/issues/23361/comments (this report). Not REST: 4 git pushes to claude/issue-23361-citations-5xx-retry.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none (no carrier) - under --probe-cause, a transfer probe that reads a 5xx yields a null verdict whose detail text says 're-run with --probe-cause' though the run already passed it; report wording in a mode no workflow runs, never passes a board - noted in the PR's Acceptance notes, not filed",
        "carrier: none (no carrier) - no board read in scripts/check-issue-citations.mjs sets a request timeout, so a hung read waits out the job timeout; predates this change and was not observed in the incident - noted, not filed",
        "carrier: none (no carrier) - a thrown error outside the retried set still surfaces as a bare 'fetch failed' that does not name the read; kept byte-identical so those exit paths are unchanged - noted, not filed"
      ],
      "gates": "At head 3becc14a: 32/32 derived commands ran, all exit 0, reconciled by --ran with exit codes (derived zero NOT-MEASURED). Also: check-issue-citations --self-test exit 0 (215 cases); the gate on its own diff exit 0; narrowed eslint exit 0 (1 file). NOT MEASURED locally and declared to CI: the dispatch-gates slow tier, 4 workflow-valued families (including --census), 11 wide-population families, 55 artifact-roster families, the path-scheduled Test Core job, and the type-check lanes (no package touched). The 15 pending-changeset families do not apply: skip-changeset, because root scripts/ publishes nothing.",
      "line_budget": "n/a",
      "deviations": "None from the contract. (a) The harness attribution reminder asked for a model-named commit trailer and a different PR footer. I used AGENTS.md's model-free trailer pair and the session-URL PR footer instead. (b) One self-caught measurement error: my first control-byte scan read head's exit status through a pipe. I re-ran it with no pipe (exit 1, none), and that reading is the one reported. (c) Token presence was checked only with the no-value form; no environment value was printed.",
      "files_changed": "scripts/check-issue-citations.mjs (+392 / -21, 1 file, vs base 4f81f315). Commits b4ca6c2b, 27383275 and 3becc14a. Worktree cleanup (rm -rf node_modules, then git worktree remove without --force) runs after this comment is read back."
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #23438 · domain:devx#2 · 2026-10-14T08:42Z

    Seat review of PR #23438 against GitHub, at head 3becc14af9 on merge base 4f81f31526. Report: 6164387890. Claim: 6163847473.

    Checklist conclusions

    • Shape: draft PR to main. Body line 1 is Fixes #23361, the body's only closing keyword. Clause-②: no is at line start. The assignee is sunhaolin.
    • Files (pulls/23438/files): scripts/check-issue-citations.mjs only, +392 / −21. That is the claim's surface. check-governed-merges.mjs --pr 23438 says not governed, 413 lines. skip-changeset is right, since root scripts/ publishes nothing. No contract review is owed: there is no published schema, no governed path and no Clause-②: yes.
    • CI at 3becc14af9, read at this act: 42 check-runs, 28 success, 11 skipped and 3 still in_progress (Test Core (1/6), Lint · PM dispatch-gates, Live half-state sweep). None is red. TypeScript Type Check is success, and the Lint & Repo Gates aggregate has not reported yet. Landing waits for every check to be green.

    Read in the diff, against the card's "What done looks like" and triage's direction 6159142526

    • Retried: readBoard wraps every board read: the head probe, each per-number probe and each enumeration page. A retry needs HTTP 500..599, or ECONNRESET or UND_ERR_SOCKET on the error's cause chain. The retried unit is the request plus its JSON body.
    • Not retried: any 4xx. A 404 stays "absent" in probeBoard and is asked once. Every other thrown error is rethrown unchanged, on its old path.
    • Never weaker: a read still failing after its ladder becomes readFailure, which is thrown, so run()'s catch ends in prerequisiteRefusal (exit 3). No branch turns a failed read into a board. The refusal text names the read, the answer and the attempt count.
    • Bounded: 3 attempts per read with 500 and 1000 ms backoff, and one budget of 4 retries per run, created once in buildBoard. The worst case is 1.5 s for a board that stays down and 3 s for one that flaps.
    • Beside the card: buildBoard now passes fetchImpl to the readers. On main, production never did, so those readers always used the global fetch. Default behaviour is unchanged.
    • makeTransferProbe stays unretried, so its 3xx stays a verdict. Only --probe-cause reaches it, and no workflow runs that.

    Evidence taken from the report, not re-run by the seat:

    • self-test: 215 cases in 10 batteries; the new retry battery has 42.
    • five ablations, each red by name and restored by blob identity;
    • live and proxy-reset readings: exit 3 after 3 CONNECTs and 2722 ms; a 503 on CONNECT is not retried;
    • 32 of 32 derived gates exit 0, reconciled by --ran.

    Out-of-scope notes, all carrier: none, in the PR's Acceptance notes: the --probe-cause wording under a 5xx, no request timeout on board reads, and a bare fetch failed on errors outside the retried set. None is a defect class this seat files. The second predates this card and was not seen in the incident.

    Landing: when every check on the current head is green, pr_ready and automerge_enable through the relay. The seat then follows it to MERGED.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — PR #23438 → 863213a90e · domain:devx#2 · 2026-10-14T09:24Z


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:devxpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions