fix(auto-merge): dispatch develop's post-merge gates — a github.token merge fires nothing (58/74 repos confirmed)#10
Conversation
… merge fires nothing GitHub suppresses workflow triggers for pushes made with the default github.token (recursive-run protection), and the org auto-merge sweep merges with exactly that token. Every commit it lands therefore arrives on develop with ZERO check runs. Measured across scitex-ai on 2026-07-22 (74 repos enumerated): 58 repos carry at least one bot-merged develop head whose GET /commits/<sha>/check-runs returns total_count 0, while user-pushed neighbours on the same branch carry 5-19 each. Runners online and idle. This is worse than a red build: a develop-health gate reads 'no checks present' as 'no red signal' and keeps merging. Green-by-absence — the hole conceals itself. workflow_dispatch is exempt from the suppression, so after a successful merge the sweep now dispatches develop's gates explicitly: - guarded on merges > 0, after the merge loop (once per call, not per PR) - skipped on dry-run (new dry_run input) - permissions.actions: write - ::error:: + exit 1 on any dispatch failure, and on an EMPTY gate set - gates auto-discovered from the DEVELOP ref (post_merge_gates overrides) Gate discovery is per-repo because leaf repos genuinely differ in workflow filenames; a hardcoded org list would 404 on half the org. Pinned by tests/test_auto_merge_dispatch.py (13 tests, file-only, no network) run by the new self-test.yml. Mutation-proven: --ref main -> 2 red; discovery ref=main -> 1 red; dispatch line deleted -> 3 red; actions:write dropped -> 1 red; restored -> 13 pass. Reference: scitex-ai/scitex-agent-container#809
Follow-up measurement: the partial mitigation is confirmed DISARMEDThe body says the autobump partial fix "only fires when 1. It exists in exactly one repo. Scanned all 74 repos' Actions workflow lists for an autobump sweep: That is the complete result set — one repo. 2. It is not armed there. This is not a permissions artifact — the same token lists the repo's variables successfully:
Net: the only code in the org that knew about this defect was single-repo, single-gate (pytest matrix only), release-gating in scope, and switched off. That combination is a sufficient explanation for the hole surviving unnoticed across 58 repos — and it is why the fix belongs here in the shared reusable workflow, unconditionally, rather than in a leaf. |
A
github.tokenmerge fires NOTHING on developGitHub suppresses workflow triggers for pushes made with the default
github.token(recursive-run protection). The org auto-merge sweep mergeswith exactly that token, so every commit it lands arrives on develop with
zero check runs.
This is worse than a red build. A develop-health gate reads "no checks
present" as "no red signal to honour" and keeps merging — green-by-absence.
The hole conceals itself: a repo can sit in this state for months and look
healthy.
Reported by
scitex-agent-container(PR #809), confirmed independently hereacross the whole org.
Evidence — measured, not inferred (2026-07-22)
Method:
gh repo list scitex-ai --limit 100→ 74 repos. For each, the last 50developcommits, classified bot (author/committergithub-actions[bot], orcommit author/committer name/email containing
github-actions) vs user. ThenGET /repos/scitex-ai/<repo>/commits/<sha>/check-runs→total_countfor upto 5 heads of each class.
58 of 74 repos have the hole confirmed — at least one bot-merged develop
head with
total_count: 0while user-pushed heads on the same branch carrychecks. Runners were online and idle; not a capacity problem.
Not confirmed (2) — every sampled bot head carried checks:
scitex-python(2/2 with checks: 20, 22),scitex-math(2/2: 47, 30).Not measurable for this defect (11) — no bot commits in the last 50
develop commits, so there is nothing to measure. These are NOT clean;
they are unmeasured:
scitex-hub,scitex-dev,scitex-writer,scitex-cards,.github,claude-code-telegrammer,scitex-storage,scitex-repl,emacs-claude-code,automated-research-demo,scitex-cloud.Unmeasurable (3):
github-action(no develop branch),scitex-ai(nodevelop branch),
scitex-os(branches API call failed — needs a retry, nota pass).
The fix
workflow_dispatchIS exempt from the suppression. After a successful mergethe sweep now dispatches develop's post-merge gates explicitly:
merges > 0, after the merge loop — N merges produce ONEdispatch round, not N
dry_runinput)permissions: actions: write(without it every dispatch 403s)::error::-loud andexit 1on any dispatch failure — the merge landed,so an un-CI'd develop head must not pass unnoticed
found nothing to dispatch" IS the green-by-absence state.
--ref develop, and auto-discovered by reading.github/workflows/*atref=developNo PAT, no bot token.
Why gates are auto-discovered rather than hardcoded
Leaf repos genuinely differ:
scitex-ioshipsscitex-io-quality-audit-on-ubuntu-latest.yml,scitex-writershipsquality-audit-on-ubuntu-latest.yml,scitex-statsships neither. Ahardcoded org-wide list would 404 on much of the org and red every sweep.
Discovery filters to active workflows that exist on develop and accept a bare
workflow_dispatch, minus a deny-list (the sweep itself, release/publish,CLA). The
post_merge_gatesinput overrides it.The wrong-ref hole is tested
"A dispatch exists but targets the wrong branch" would satisfy a naive
"is there a dispatch?" check while leaving develop just as unverified.
tests/test_auto_merge_dispatch.pypins it from both sides(
test_every_dispatch_targets_develop,test_no_dispatch_targets_a_non_develop_ref) and pins the discovery ref too(
test_gate_discovery_reads_the_develop_ref).13 tests, file-only (parse the YAML, assert on the shell body) — no network,
no
gh, cannot flake. Run by the newself-test.yml.Mutation-proven (each mutation applied, tests run, workflow restored):
--ref develop→--ref mainref=develop→ref=maingh workflow runlineactions: writeTHIS FIX IS INERT UNTIL IT REACHES THE DEFAULT BRANCH
GitHub reads
schedule- andcheck_suite-triggered workflow definitions fromthe default branch. Merging this to
developalone changes nothing atruntime.
Both halves are required:
scitex-ai/.github→main. This repo's default branch ismain, andworkflow_callresolves the reusable workflow from the default branch.Until this lands on
main, no caller gets the new body.(
main). Already a documented requirement of this file; restated becauseit is the half that costs a day when missed.
Landing this on
developonly will look merged and do nothing.Second, INDEPENDENT mechanism found while measuring — reported separately
Hypothesis handed over by
scitex-ui, now confirmed: main-vs-developcheck context-name divergence. Branch protection matches required contexts
by name, so a required context that no workflow emits under that name is
recorded as absent, not red — a second route to green-by-absence.
Measured: 65 of 74 repos have readable develop protection; 60 declare required
contexts. 20 have at least one required context that did not appear on the
develop head. The pattern is a reusable-caller prefix mismatch, in both
directions:
pytest-matrix / pytest-matrix-on-ubuntu-py3.11, emitted barepytest-matrix-on-ubuntu-py3.11— figrecipe, scitex-orochi, scitex-app,scitex-audio, scitex-dataset, scitex-session, scitex-repl, scitex-math,
scitex-linalg, scitex-git, scitex-capture, scitex-web, scitex-compat
scitex-clew, scitex-ml
Five more (
scitex-repro,scitex-db,scitex-tex,scitex-logging,scitex-nn) emitted nothing at all on their develop head — those heads arebot-merged, so they are casualties of mechanism 1, not evidence for mechanism 2.
Underlying cause is broad workflow drift between
mainanddevelop: 59 of74 repos differ in workflow files and/or job ids across the two branches. The
most common single divergence is
auto-merge-to-develop.yamlitself —jobs: [automerge]on main vsjobs: [call]on develop, in roughly 20 repos.This PR does not fix that. It needs its own change (align required contexts
with emitted names, or standardise the caller job id org-wide) and its own
evidence pass.
Also checked: the partial mitigation that hid this
scitex-agent-container'sautobump-release-sweep.yamlalready dispatchespytest-matrix-on-ubuntu-py3-11-3-12-3-13.ymlon a no-checks develop head(lines 413-419), and its header already documents that a
github.tokenpush"fires NOTHING". But it is narrow on three axes: it only fires when the repo
variable
AUTOBUMP_ENABLED == 'true', it only dispatches the pytest matrix(its own comment notes quality-audit and import-smoke lack
workflow_dispatch), and it only runs as release-gating recovery.autobump-release-sweep.yamlexists in exactly one repo org-wide. Adisarmed, single-repo, single-gate partial fix is a plausible reason nobody
noticed the general hole.
🤖 Generated with Claude Code
https://claude.ai/code/session_017sFEMCtcsCYDP1pAszwRwm