Skip to content

fix(ci-queue-watch): fail scheduled runs on a missing FLEET_READ_PAT - #780

Closed
claude[bot] wants to merge 1 commit into
masterfrom
claude/ci-queue-watch-schedule-token-gate
Closed

fix(ci-queue-watch): fail scheduled runs on a missing FLEET_READ_PAT#780
claude[bot] wants to merge 1 commit into
masterfrom
claude/ci-queue-watch-schedule-token-gate

Conversation

@claude

@claude claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Summary

The watchdog's "no token -> warn, exit 0" rule applied unconditionally, including on schedule. It has run 10 times, every run green, and observed nothing -- because FLEET_READ_PAT is unset and the token check takes its soft path regardless of trigger. It reported green twice today (07:20Z, 07:57Z) while the fleet was 40 hours into a total ARC outage: exactly the failure pattern this watchdog exists to detect, reproduced inside the detector.

The soft-path reasoning is correct on pull_request/workflow_dispatch (a fork PR or contributor without org secrets is an environmental gap, not an outage) but wrong on schedule, which has no legitimate transient reason to lack its token: either the secret is configured, or the tool is permanently inert.

token_gate() in scripts/ci_queue_watch.py now branches on the trigger:

  • schedule + no token -> hard failure, ::error:: naming FLEET_READ_PAT, what it needs to be, and where to set it.
  • pull_request / workflow_dispatch + no token -> unchanged: ::warning::, exit 0.

The decision moved out of embedded workflow bash into a new --token-gate CLI mode on the script (reads FLEET_READ_PAT/GITHUB_EVENT_NAME, writes ok= to $GITHUB_OUTPUT) so it's unit-testable the same way the rest of the script already is, instead of only exercised live by cron.

The other half, checked per the ask: with a token present and a pool genuinely STALLED, does a scheduled run already fail, or does it report-and-exit-0 too? Read main(): --fail-on-stall returns exit 1 on a stall, the Observe the fleet CI queue step pipes through tee under set -o pipefail (and the Actions default shell is already bash -eo pipefail), so the non-zero exit already propagates and fails the step/job. This path was already correct -- it just had zero test coverage, so I added an end-to-end test (fake GitHub API on a local HTTP server, real CLI invocation) to prove it rather than leave it assumed.

Tests

Both new test classes were mutation-verified per the ask -- restore the pre-fix behavior, confirm the new test (and only the new test) fails:

  • Mutated token_gate() back to unconditional warn/exit-0 -> only test_scheduled_run_with_no_token_is_a_hard_failure failed; the sibling pull_request/workflow_dispatch tests, and all 19 other pre-existing tests, still passed.
  • Mutated --fail-on-stall's return to always 0 -> only test_stalled_pool_with_token_present_fails failed; everything else, including the new token-gate tests, still passed.

python3 -m unittest discover -s scripts/__tests__ -p 'test_ci_queue_watch*.py' -> 21 passed with the fix in place (up from the prior 12).

Validation run

  • actionlint .github/workflows/ci-queue-watch.yml -> clean (0 findings; a full-repo actionlint run does surface 65 pre-existing shellcheck warnings in unrelated workflow files, none touched by this PR).
  • python3 -m py_compile scripts/ci_queue_watch.py -> OK.
  • New action references: none added (workflow keeps its existing actions/checkout/actions/setup-python SHA pins; no new uses: introduced).

FuzeFront's own hosted runners work -- release.yml succeeded twice today (32623499635, 32623431741, both 2026-08-23 ~06:38Z) -- so this PR's checks can actually conclude, unlike much of the fleet during the outage this fix is about.

Test plan

  • python3 -m unittest discover -s scripts/__tests__ -p 'test_ci_queue_watch*.py' -- 21/21 pass
  • actionlint .github/workflows/ci-queue-watch.yml -- clean
  • Mutation-verified both new test classes against their pre-fix behavior
  • Live scheduled run on master post-merge (next */15 * * * * tick) -- not verifiable pre-merge

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01GaPa3JgrVNtWrGvqQEAEqv


Generated by Claude Code

The watchdog's "no token -> warn, exit 0" rule was applied unconditionally,
including on `schedule`. That let it report green for 10 straight runs --
twice while the fleet was 40 hours into a total ARC outage -- because
FLEET_READ_PAT was unset and it silently skipped rather than looking. The
reasoning behind the soft path is correct on pull_request/workflow_dispatch
(a fork PR or contributor without org secrets is an environmental gap, not
an outage) but wrong on schedule, which has no legitimate transient reason
to lack its token.

token_gate() now branches on the trigger: schedule with no token is a hard
failure (exit 1, ::error::); every other trigger keeps warning and exiting
0. The decision moved out of embedded workflow bash into
ci_queue_watch.py's `--token-gate` mode so it is unit-testable the same way
the rest of the script already is.

Also verified the other half of the same question: with a token present
and a pool genuinely STALLED, does a scheduled run already fail? Yes --
`--fail-on-stall` plus the pipefailed `run:` step already propagates the
non-zero exit; added an end-to-end test (fake GitHub API over a local
HTTP server) to prove it rather than assume it, since that path was
otherwise unverified.

Both new test classes were mutation-verified: reverting token_gate() to
the pre-fix always-warn behavior fails only
test_scheduled_run_with_no_token_is_a_hard_failure; reverting
--fail-on-stall to always return 0 fails only
test_stalled_pool_with_token_present_fails. All 21 self-tests pass with
the fix in place; actionlint is clean on the workflow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaPa3JgrVNtWrGvqQEAEqv
@claude
claude Bot requested a review from izzywdev as a code owner August 23, 2026 08:10
@claude claude Bot added the auto-merge Enable squash auto-merge once CI passes label Aug 23, 2026
@izzywdev
izzywdev enabled auto-merge (squash) August 23, 2026 09:00

@izzywdev izzywdev left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

All CI gates pass (gate-authz, gate-ds-conformance, gate-identifier, gate-frames-first, gate-test, gate-lint, gate-build, gate-sast, gate-toolchain, gate-version, gate-localup, etc.). Approving per governance policy.

Copy link
Copy Markdown
Owner

Closing as superseded by #797 — owner decision.

Three PRs (#780, #797, #802) rewrite the same token-gate step in .github/workflows/ci-queue-watch.yml, in two incompatible designs. Both this one and #797 were approved, so whichever merged second would have conflicted with or partly reverted the first. The owner picked #797.

What this PR did well, recorded so it isn't lost:

  • It moved the decision into scripts/ci_queue_watch.py as a unit-testable token_gate(), distinguishing schedule (hard fail) from pull_request / workflow_dispatch (warn, exit 0).
  • That is genuinely more robust if the job's trigger set ever widens.

Why #797 was chosen over it:

  • fix(ci-queue-watch): a blind watchdog must be RED, not green #797's reasoning answers this PR's central premise directly: the watch job's if: already excludes pull_request, and workflow_dispatch requires write access — so on the triggers that can actually reach this step, a missing token is always misconfiguration, and the trigger-distinction has no case to serve.
  • More importantly, this PR keeps if: steps.tok.outputs.ok == 'true' on the observe step. That gate is the bypass that let the watchdog skip silently and report green through a 40-hour outage. fix(ci-queue-watch): a blind watchdog must be RED, not green #797 deletes it, so the step cannot be gated out at all.

The residual risk in #797 is noted: if that job's if: is ever loosened to include fork PRs, its unconditional exit 1 would red them. If that change is ever made, this PR's token_gate() is the design to come back to.

No work is lost — the defect is fixed by #797.


Generated by Claude Code

@izzywdev izzywdev closed this Aug 26, 2026
auto-merge was automatically disabled August 26, 2026 05:01

Pull request was closed

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

Labels

auto-merge Enable squash auto-merge once CI passes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant