Repository navigation
fix(#8111): consult config.base.yaml for kill_switch in GitHub and GitLab dispatch - #8141
Conversation
…tHub and GitLab dispatch Both forges' built-in-stage dispatch checks only ever read kill_switch from .fullsend/config.yaml, so a central operator setting kill_switch: true in .fullsend/config.base.yaml alone had no effect on built-in stages (triage, code, review, fix, retro, prioritize). This is a parity gap, not a greenfield need: IsKillSwitchActive() already layers overlay then base then a compiled default of false, and is already honored by the Jira polling path, the managed-config-push safety gate, and the harness-dispatch job in this same workflow file. - GitHub (reusable-dispatch.yml): add config.base.yaml to the route job's sparse-checkout list, and extend the kill-switch check to consult it when config.yaml omits the key. - GitLab (run-agent-job.sh): fetch config.base.yaml the same way config.yaml is fetched, and apply the same override semantics. Both checks now match IsKillSwitchActive()'s resolution order: an explicit kill_switch value in config.yaml wins if set (including false), otherwise config.base.yaml applies, otherwise false. GitLab's pre-existing fail-open behavior on a YAML parse error (log a warning and proceed) is left unchanged. Adds Go subprocess tests for the GitLab script's new fallback/override behavior, and a new e2e behaviour-test scenario proving a config.base.yaml-only kill switch blocks GitHub dispatch. Signed-off-by: fmuntean <fmuntean@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
E2E tests did not runE2E tests run automatically for org/repo members and collaborators on pull requests. For other contributors, a maintainer with write access must add the See E2E testing guide for details. |
PR Summary by QodoHonor base-config kill switch in GitHub and GitLab dispatch
AI Description
Diagram
High-Level Assessment
Files changed (9)
|
Code Review by Qodo
1.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
/fs-review |
|
🤖 Finished Review · ✅ Success · Started 7:18 AM UTC · Completed 7:29 AM UTC Commit: Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $2.93 |
|
Risk Assessment: moderate (2/5) DetailsModerate risk driven by CI workflow and protected path modifications with high historical churn in dispatch files, mitigated by strong test coverage, zero dependency changes, and complete alignment with linked issue acceptance criteria. Previous runRisk Assessment: moderate (2/5) DetailsModerate risk driven by CI workflow and protected path modifications with high historical churn in dispatch files, mitigated by strong test coverage, zero dependency changes, and complete alignment with linked issue acceptance criteria. Previous run (2)Risk Assessment: moderate (2/5) DetailsModifies CI dispatch workflow and runner scripts touching protected paths with high historical churn and regression fixes, but well-aligned with linked issue scope, fresh discussion, and comprehensive test coverage yielding a moderate risk score. Previous run (3)Risk Assessment: moderate (2/5) DetailsModifies CI dispatch workflow and runner scripts touching protected paths with high historical churn and regression fixes, but well-aligned with linked issue scope, fresh discussion, and comprehensive test coverage yielding a moderate risk score. |
ReviewFindingsMedium
Low
Next steps:
Previous runReviewFindingsMedium
Low
Next steps:
Previous run (2)ReviewFindingsMedium
Low
Next steps:
Previous run (3)ReviewFindingsHigh
Medium
Next steps:
|
- GitLab (run-agent-job.sh): resolve the overlay first and only parse config.base.yaml when the overlay is silent on kill_switch. This treats an overlay kill_switch: null the same as an omitted key (falls through to base, matching Go's IsKillSwitchActive()), and keeps a malformed base.yaml from ever suppressing an explicit overlay value, since base is no longer parsed unconditionally. - GitHub (reusable-dispatch.yml): compare the resolved kill_switch value case-insensitively, so a valid YAML boolean spelling like kill_switch: True halts dispatch the same as lowercase true. Signed-off-by: fmuntean <fmuntean@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
waynesun09
left a comment
There was a problem hiding this comment.
Review sweep: 2 findings (1 HIGH, 1 MEDIUM) on the kill-switch layering, inline below.
…update docs Addresses the remaining findings from PR review: - GitHub (reusable-dispatch.yml): switch to `yq -o=json '.kill_switch'` for both the overlay and base reads. Raw yq output preserves the source spelling of a scalar, so a bare `kill_switch:`, `~`, or `Null` printed as something other than the literal string "null" and was treated as an explicit overlay value, skipping an active base kill switch. -o=json normalizes every null spelling to `null` and every boolean case variant to lowercase, so the previous case-insensitive comparison workaround is no longer needed. - pkg/behaviourtest/steps/cleanup.go: ValidateSlotClean's layered parse fails closed on a malformed config.base.yaml before the overlay is ever examined (base is parsed first). A malformed base left behind by a failed scenario was therefore masking real overlay staleness (e.g. an overlay kill_switch: true left on). Fall back to an overlay-only parse in that case so the overlay is still checked, and report the malformed base itself as stale state. - docs/contributing/config-source-inventory.md: move the kill_switch row to the "already layered" table now that both forges' direct reads consult config.base.yaml. - docs/guides/dev/behaviour-testing.md: document the new "the kill switch is active in config.base.yaml" step, and note the kill switch can be set in either file. Adds regression tests for both code fixes, each confirmed to fail against the pre-fix code. Signed-off-by: fmuntean <fmuntean@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Site previewPreview: https://22a2ea4f-site.fullsend-ai.workers.dev Commit: |
waynesun09
left a comment
There was a problem hiding this comment.
Review sweep: 1 MEDIUM finding on the route kill-switch check, inline below.
…ub route check
GitHub's yq -o=json-based check only recognized true/false case
variants; go-yaml's lenient *bool decoding (used by
IsKillSwitchActive()) and GitLab's PyYAML-based check both already
treat yes/on as true and no/off as false. Add yes/"yes"
(case-insensitive) recognition to the GitHub check, closing two
fail-open gaps on that spelling. on/off and single-letter y/n remain
intentionally unhandled (documented as a known limitation, not chased
to full parity).
Verified GitLab's existing check needs no change: its
('true','yes','1','on') string-tuple comparison already matches
regardless of whether PyYAML coerces the YAML scalar to a bool or
leaves it as a string.
Adds test coverage for the yes/no spelling matrix on both forges.
Signed-off-by: fmuntean <fmuntean@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
@rh-hemartin thanks for the review and approval. How do I get the PR merged ? I'm still unclear if it's automated and goes to the merge queue after approval or if the merge is manual done by maintainers only. |
Signed-off-by: fmuntean <fmuntean@redhat.com>
waynesun09
left a comment
There was a problem hiding this comment.
Review sweep: 1 HIGH finding on the route kill-switch check, inline below.
|
🤖 Finished Review · ✅ Success · Started 10:42 AM UTC · Completed 10:54 AM UTC Commit: Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $3.53 |
…h check
The -o=json switch (added earlier in this branch to fix the
null-spelling bypass) regressed quoted scalars: a quoted
kill_switch: "true"/'true' now prints as the JSON string "true"
instead of the bare boolean true, so the existing
"${KILL_SWITCH,,}" == "true" comparison no longer matched it,
silently failing open. The old pre-branch step (yq without -o=json)
had accidentally halted on this case, since its non-JSON output
rendered a quoted string identically to a bare boolean.
Strip a leading/trailing double quote from KILL_SWITCH before
comparing, so true/"true"/'true'/yes/"yes" are all recognized
again. on/off and single-letter y/n remain intentionally unhandled.
Adds overlay and base test rows for the quoted-true regression.
Signed-off-by: fmuntean <fmuntean@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
🤖 Finished Review · ✅ Success · Started 11:02 AM UTC · Completed 11:13 AM UTC Commit: Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $3.38 |
|
🤖 Finished Review · ✅ Success · Started 9:24 AM UTC · Completed 9:35 AM UTC Commit: Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $2.90 |
waynesun09
left a comment
There was a problem hiding this comment.
LGTM. Remaining review items are tracked in #8455.
Fixes #8111
Problem
Both forges' built-in-stage dispatch checks only ever read
kill_switchfrom
.fullsend/config.yaml. A central operator settingkill_switch: truein.fullsend/config.base.yamlalone had no effecton built-in stages (triage, code, review, fix, retro, prioritize).
This was a parity gap, not a greenfield need:
IsKillSwitchActive()already layers overlay (
config.yaml) → base (config.base.yaml) →compiled default (
false), and that resolution order is alreadyhonored by the Jira polling path, the managed-config-push safety gate,
and the harness-dispatch job in this very workflow file. Only the
route-stage kill-switch checks on GitHub and GitLab were out of sync.
Fix
reusable-dispatch.yml): addconfig.base.yamlto theroute job's sparse-checkout list, and extend the kill-switch check to
consult it when
config.yamlis silent on the key.run-agent-job.sh): fetchconfig.base.yamlthe sameway
config.yamlis fetched, and apply the same override semantics.Both checks now follow
IsKillSwitchActive()'s overlay → base →default resolution order: an explicit
kill_switchvalue inconfig.yamlwins if set (includingfalse), otherwiseconfig.base.yamlapplies, otherwisefalse.Known limitation — boolean spelling, not resolution order: Go's
*booldecoding (gopkg.in/yaml.v3) and GitLab's PyYAML-based checkboth already treat
yes/onas true andno/offas false. Thepre-PR GitHub check (
yq '.kill_switch // false', no-o=json) wascase-sensitive — it did not recognize
True/TRUE— but accidentallyhalted on quoted
kill_switch: "true"/'true'as a side effect ofyq's non-JSON output rendering a quoted string identically to a bare
boolean, not because it understood quoting. This PR's
-o=jsonswitchfixes the null-spelling and case-variant gaps, adds
yes/norecognition, and strips a quoted scalar's surrounding quotes before
comparing, so
true/"true"/'true'/yes/"yes"are all correctlyrecognized on GitHub.
on/offand the single-lettery/nformsremain unhandled by deliberate choice — low real-world likelihood as
kill_switchvalues, and chasing exhaustive parity with go-yaml's ownlenient-but-uneven bool coercion (which separately diverges on bare
1, which Go rejects as a decode error) isn't worth the complexityhere. See
this review thread
for the full analysis.
Unchanged behavior: GitLab's pre-existing fail-open handling of a
YAML parse error (log a
WARNINGand treat as "no kill-switch",continuing) is intentionally left as-is — not expanded to fail-closed.
This diverges from
docs/glossary.md's "Fail-Open" entry, whichclassifies kill switch as a fail-closed gate; closing that gap is left
for separate follow-up (noted in the issue).
Testing
internal/scaffold/gitlab_job_scripts_test.go: new table-drivensubprocess test exercising
run-agent-job.sh's kill-switch checkend-to-end — base-only activation, overlay-overrides-base, neither
set, and the malformed-YAML fail-open path.
e2e/behaviour/features/dispatch/fork-dispatch.feature: new scenarioproving a
config.base.yaml-only kill switch blocks GitHubdispatch, with supporting step definitions and cleanup/validation
wiring in
pkg/behaviourtest/.internal/scaffold/github_kill_switch_test.go/internal/scaffold/gitlab_kill_switch_test.go: added rows for everyYAML null spelling (bare key,
~,Null), a malformed-base-survivescase, and the
yes/"yes"/no/on/off/y/n/"true"/'true'boolean-spelling matrix (overlay and base) on both forges, locking in
the known limitation above.