fix(ci): rescue daily-regen from GitHub's silently dropped cron ticks#9649
Conversation
GitHub's scheduler has been dropping daily-regen.yml's schedule events in multi-day streaks since early June (e.g. only 5 scheduled runs in the last 13 days instead of ~130), while less-frequent crons in the same repo (watchdog-stuck-jobs, bot-serving-check) kept firing with their usual 2-3 h delay. Every run that did start succeeded, so nothing alarmed and the oldest specs simply stopped being refreshed. Two defences: - daily-regen's cron moves off the top of the hour (:00 -> :17), the documented high-load window in which GitHub delays/drops schedule events. - watchdog-stuck-jobs gains a cron-liveness section: if no daily-regen run (any trigger) has started for >10 h outside the 17-21 UTC Berlin-evening quiet window, it re-dispatches the workflow with default inputs, capping future starvation at roughly half a day. Also documents watchdog-stuck-jobs.yml in docs/workflows/overview.md, which had been missing from the workflow table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vs46U1fqUJQcpf2jE3YdN
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vs46U1fqUJQcpf2jE3YdN
There was a problem hiding this comment.
Pull request overview
This PR improves the reliability of the repository’s scheduled “daily regen” maintenance by moving the daily-regen.yml cron away from GitHub’s top-of-hour overload window and adding a watchdog-based liveness rescue to re-dispatch the workflow if it appears starved.
Changes:
- Shift
daily-regen.ymlscheduled ticks from:00to:17on even UTC hours to reduce missedscheduleevents. - Extend
watchdog-stuck-jobs.ymlto detect prolonged absence ofdaily-regenruns and re-dispatch it outside the quiet window. - Document the updated scheduling behavior and watchdog responsibilities, and add a matching
CHANGELOG.mdentry.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
docs/workflows/overview.md |
Updates the workflow overview table to include watchdog-stuck-jobs.yml and clarifies the daily-regen.yml cadence/minute offset. |
CHANGELOG.md |
Adds an [Unreleased] fix entry describing the cron starvation mitigation and watchdog rescue. |
.github/workflows/watchdog-stuck-jobs.yml |
Adds a “cron-liveness” section to re-dispatch daily-regen.yml after >10h without a run outside the quiet window. |
.github/workflows/daily-regen.yml |
Moves the cron schedule off the top of the hour (to minute 17) and documents the rationale and watchdog rescue behavior. |
Review feedback: a manual daily-regen run on a non-default branch must not mask a starved main schedule, and startedAt is null while the newest run is still queued (which the previous query misread as 'no runs found'). Scope gh run list to --branch main and measure the gap from createdAt, which is always populated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vs46U1fqUJQcpf2jE3YdN
Review feedback: integer-hour truncation could print '10h (> 10h)' when the real gap is 10h59m. Log h+mm so the message matches the threshold evaluation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vs46U1fqUJQcpf2jE3YdN
Review feedback: the rescue keys off the newest run's createdAt, not whether a run has started. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vs46U1fqUJQcpf2jE3YdN
…under CET Review feedback: the 18-21 UTC window was documented in Berlin/CEST terms, but Actions cron is UTC year-round, so the local-time mapping shifts by an hour under winter time. Document the accepted drift in both daily-regen and the watchdog liveness guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vs46U1fqUJQcpf2jE3YdN
Review feedback: 18/20 UTC are also even hours but sit in the quiet window, and the watchdog rescue's worst case is just under a day (the >10h mark can land in the skipped 17-21 UTC checks), not half a day. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vs46U1fqUJQcpf2jE3YdN
Review feedback: NOW is captured before the A/B scans, so the gap was underestimated by however long those took. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vs46U1fqUJQcpf2jE3YdN
…stop Pins claude-code-action to 11ba604 (last working, Jul 1) and stops the job right after the Claude step to check whether Claude produces the impl file + PNGs (permission_denials). Disables the failure handler so the run cannot comment on / retry #1010. Dispatched against this branch only via workflow_dispatch ref; main is untouched. Reverted immediately after. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vs46U1fqUJQcpf2jE3YdN
| # ======================================================================== | ||
| # VALIDATION TEST — inspect what Claude produced, then STOP before any | ||
| # side-effecting steps (PR / GCS / issue comment). Remove after test. | ||
| # ======================================================================== | ||
| - name: TEST — inspect Claude output and stop (validation run) | ||
| if: always() | ||
| env: |
| - name: Handle generation failure | ||
| if: failure() && steps.issue.outputs.number != '' | ||
| if: false # VALIDATION: disabled during test so it can't comment/retry on #1010 |
| - name: Run Claude Code to generate implementation | ||
| id: claude | ||
| continue-on-error: true | ||
| timeout-minutes: 60 | ||
| uses: anthropics/claude-code-action@1298632ce7736903d02a1435002705aa2a594a6c # v1 | ||
| uses: anthropics/claude-code-action@11ba60486e4aec9ddfeafcf4bb3f00b028ac2c16 # v1 (VALIDATION: July-1 pin, revert after) | ||
| with: |
…tion' daily-regen re-generates implementations for the oldest specs (dispatches bulk-generate), not specs themselves; clearer wording per review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vs46U1fqUJQcpf2jE3YdN
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
.github/workflows/impl-generate.yml:425
- The workflow is pinned to a temporary claude-code-action SHA with a "revert after" note. This kind of validation-only pin is easy to forget and can silently change behavior vs the previously vetted pin; if this PR’s scope is daily-regen/watchdog reliability, this should be reverted to the prior pin (or moved to a dedicated validation PR).
uses: anthropics/claude-code-action@11ba60486e4aec9ddfeafcf4bb3f00b028ac2c16 # v1 (VALIDATION: July-1 pin, revert after)
.github/workflows/impl-generate.yml:466
- This validation step is unconditional (
if: always()) and exits 1, which will force every impl-generate run to fail and stop the pipeline before PR/GCS/issue-comment side effects. If this PR is intended to ship, this needs to be removed or at least disabled/guarded so normal runs can complete.
- name: TEST — inspect Claude output and stop (validation run)
if: always()
.github/workflows/impl-generate.yml:1046
Handle generation failureis fully disabled (if: false), which removes the workflow’s normal failure reporting/auto-retry behavior. If the validation block above is being removed/disabled, this should be restored so failures still get tracked and capped retries behave as designed.
- name: Handle generation failure
if: false # VALIDATION: disabled during test so it can't comment/retry on #1010
Validation run refuted the action-version hypothesis: the July-1 action pin (11ba604) produced the same empty result (no PNGs, no file changes). Revert that pin to the current SHA and instead un-hide Claude's output so the next run's logs show exactly which tool calls are permission-denied. Diagnostic only; the hermetic TEST stop + disabled failure handler stay. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vs46U1fqUJQcpf2jE3YdN
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
.github/workflows/impl-generate.yml:456
- Same as the primary Claude step: leaving
show_full_output: trueenabled in the retry path will also increase verbosity for normal runs after merge. Revert to the default (or gate behind an opt-in) so production logs stay stable.
uses: anthropics/claude-code-action@1298632ce7736903d02a1435002705aa2a594a6c # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# VALIDATION (diagnostic only, revert after): un-hide Claude's output so
# the logs reveal exactly which tool calls are being permission-denied.
show_full_output: true
claude_args: "--model ${{ steps.inputs.outputs.model }}"
# bulk-generate dispatches us from the github-actions bot; explicitly allow it.
allowed_bots: '*'
.github/workflows/impl-generate.yml:473
- This validation step is unconditional (
if: always()) and ends withexit 1, which will make every impl-generate run fail and prevents all downstream steps (metadata/PR/review) from executing. This will effectively disable the implementation pipeline if merged.
- name: TEST — inspect Claude output and stop (validation run)
if: always()
env:
.github/workflows/impl-generate.yml:1052
- Failure handling is disabled via
if: false, so generation failures would stop being tracked/commented and auto-retries would no longer occur. If the validation-only changes are removed/disabled, this condition should be restored so normal failure handling continues to work.
- name: Handle generation failure
if: false # VALIDATION: disabled during test so it can't comment/retry on #1010
| uses: anthropics/claude-code-action@1298632ce7736903d02a1435002705aa2a594a6c # v1 | ||
| with: | ||
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | ||
| # VALIDATION (diagnostic only, revert after): un-hide Claude's output so | ||
| # the logs reveal exactly which tool calls are being permission-denied. | ||
| show_full_output: true | ||
| claude_args: "--model ${{ steps.inputs.outputs.model }}" | ||
| # bulk-generate dispatches us from the github-actions bot; explicitly allow it. | ||
| allowed_bots: '*' |
Root cause of the pipeline breakage is found and fixed in PR #9651 — no further validation runs on this branch are needed or even possible: the action's OAuth token exchange 401s any run whose workflow file differs from the default branch ('Workflow validation failed. The workflow file must exist and have identical content to the version on the repository's default branch.'). That 401 is also why the earlier July-1-pin validation run produced an empty result: Claude never started, so it neither confirmed nor refuted the action-version hypothesis. The actual mechanism (confirmed by local repro): CLI 2.1.171+ ignores permissions.allow from .claude/settings.json in untrusted workspaces, and CI checkouts are never trusted; explicit --settings in claude_args is honored and fixes it. impl-generate.yml is now byte-identical to main again, leaving this PR with only its cron-offset + watchdog-liveness content. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Coordination note (from the local session): root cause found & fixed elsewhere — no more validation runs on this branch. To any watcher of this PR (including the parallel Claude session that pushed
Please do not push further diagnostic commits or dispatch further validation runs here. Post-merge confirmation of #9651 will be a single real |
…fresh-broken-yzk1d9 # Conflicts: # CHANGELOG.md
Summary
daily-regen.yml's cron ticks in multi-day streaks since early June (full 10×/day cadence until Jun 3, then gaps of 4–13 days: Jun 3→8, Jun 8→20, Jul 1→14, Jul 14→20; only 5 scheduled runs in the last 13 days instead of ~130). Less-frequent crons in the same repo (watchdog-stuck-jobsevery 6 h,bot-serving-checkdaily) kept firing throughout, and every daily-regen run that did start was green — so the workflow itself is healthy and nothing alarmed. All scheduled runs here start with a ~2–3.5 h scheduler delay, and GitHub documents top-of-hour overload as a cause for delayed/droppedscheduleevents; daily-regen is the repo's most frequent cron (2 h spacing ≈ the observed delay) and the only one affected.0 0,2,…→17 0,2,…), the documented mitigation for the high-load window.watchdog-stuck-jobs.yml(which fires reliably) gains a cron-liveness section: if no daily-regen run of any trigger type has started for >10 h outside the 17–21 UTC Berlin-evening quiet window, it re-dispatchesdaily-regen.ymlwith default inputs via the existing DRY_RUN-awaredispatchhelper — capping any future starvation at roughly half a day instead of two weeks.watchdog-stuck-jobs.ymlrow to the workflow table indocs/workflows/overview.md.Test plan
yaml.safe_load) for both edited workflowsrun:script passesbash -n.github/workflows/changes): after merge, confirm ticks land at :17 and that the next watchdog runs log the liveness lineChecklist
CHANGELOG.mdupdated under[Unreleased](Keep-a-Changelog categories,bold-titled bullets, PR ref) — required for every non-pipeline PR
docs/reference/,docs/workflows/,docs/contributing.md) if behavior changed🤖 Generated with Claude Code
https://claude.ai/code/session_014vs46U1fqUJQcpf2jE3YdN
Generated by Claude Code