ci: stop cancelling in-progress runs on main - #6506
Conversation
The concurrency group is keyed on github.ref, so every push to main shares one key and cancel-in-progress: true evicts the previous merge's checks mid-flight. A cancelled check is not a failed check, so branch protection and the commit status settle on a green-looking main while the verification that would have caught a regression never completed. Gate the cancellation on the ref instead. Pull-request and merge-queue runs each carry their own ref, so superseded runs there still cancel unchanged. Fixes #6384 Part of #6373
A quoted "true" is equally truthy to GitHub but parses as a string, so the type-based comparison passed over it.
✅MegaLinter analysis: Success✅ Linters with no issuesactionlint, bash-exec, git_diff, hadolint, jscpd, jsonlint, lychee, markdown-table-formatter, markdownlint, prettier, prettier, shellcheck, shfmt, stylelint, syft, trivy-sbom, trufflehog, v8r, v8r, yamllint Notices📣 MegaLinter 9.5.0 is out! Discover the new features and security recommendations in the release announcement. (Skip this info by defining See detailed reports in MegaLinter artifacts
|
What I exercised, and what I could notPull-request cancellation still works — observed on GitHub's real evaluator, not reasoned. This PR got a free natural experiment: I pushed twice, 73 seconds apart, with the new expression already in place.
For a The default-branch half cannot be proven pre-merge. It is the complementary branch of that same evaluated expression, but demonstrating it needs two merges to A correction to the issue's premise#6384 says "this repo gates So the merge-queue interaction is currently inert. The expression is still correct if a queue is enabled later: each queue entry gets its own Scope check
The same defect exists in five other repositories — tracked separately as devantler-tech/monorepo#2690, not fixed here. |
Requesting review at @coderabbitai review |
|
✅ Action performedReview finished.
|
📝 WalkthroughWalkthroughThe CI workflow now gives Possibly related issues
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci.yaml:
- Around line 29-33: Make the main-branch concurrency group unique per workflow
run by using github.run_id when github.ref is refs/heads/main, while retaining
github.ref-based groups for pull requests and merge groups; update the
corresponding expectations in internal/ciharness/ci_workflow_test.go at lines 42
and 83-93 to validate the run-specific main group and preserved ref-based
behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 260535c4-f7d3-4b42-bdec-3052b8443e30
📒 Files selected for processing (2)
.github/workflows/ci.yamlinternal/ciharness/ci_workflow_test.go
📜 Review details
⏰ Context from checks skipped due to timeout. (18)
- GitHub Check: 🧪 System Test (Docker) (K3s, Docker, true, --cni Calico --csi Disabled --load-balancer Disable...
- GitHub Check: 🧪 System Test (Docker) (K3s, Docker, true, --gitops-engine ArgoCD --local-registry ghcr.io/dev...
- GitHub Check: 🧪 System Test (Docker) (Vanilla, Docker, true, --gitops-engine Flux --local-registry ghcr.io/d...
- GitHub Check: 🧪 System Test (Docker) (K3s, Docker, true, --gitops-engine Flux --local-registry ghcr.io/devan...
- GitHub Check: 🧪 System Test (Docker) (K3s, Docker, true, --name system-test-cluster --cni Cilium --csi Enabl...
- GitHub Check: 🧪 System Test (Docker) (Vanilla, Docker, true, --gitops-engine ArgoCD --local-registry ghcr.io...
- GitHub Check: 🧪 System Test (Docker) (KWOK, Docker, true, --gitops-engine Flux --local-registry ghcr.io/deva...
- GitHub Check: 🧪 System Test (Docker) (KWOK, Docker, true, --gitops-engine ArgoCD --local-registry ghcr.io/de...
- GitHub Check: 🧪 System Test (Docker) (Vanilla, Docker, true, --name system-test-cluster --cni Cilium --csi E...
- GitHub Check: 🧪 System Test (Docker) (KWOK, Docker, true, --name system-test-cluster --cni Cilium --csi Enab...
- GitHub Check: 🧪 System Test (Docker) (KWOK, Docker, true, --cni Calico --csi Disabled --load-balancer Disabl...
- GitHub Check: 🧪 System Test (Docker) (Vanilla, Docker, true)
- GitHub Check: 🧪 System Test (Docker) (K3s, Docker, true)
- GitHub Check: 🧪 System Test (Docker) (Vanilla, Docker, true, --cni Calico --csi Disabled --load-balancer Dis...
- GitHub Check: 🧪 System Test (Docker) (KWOK, Docker, true)
- GitHub Check: 🛡️ Vulnerability Scan
- GitHub Check: 📊 Code Coverage
- GitHub Check: Analyze (go)
🧰 Additional context used
📓 Path-based instructions (4)
.github/workflows/*.yaml
📄 CodeRabbit inference engine (AGENTS.md)
Validate workflow changes with
mega-linter-runner -f go; MegaLinter runsactionlintfor GitHub Actions workflows.
Files:
.github/workflows/ci.yaml
**/*.go
📄 CodeRabbit inference engine (AGENTS.md)
**/*.go: Use Go 1.26.1 or newer, matching the version declared ingo.mod.
All user-supplied file path arguments in CLI commands must be canonicalized withfsutil.EvalCanonicalPathbefore use; create parent directories first for new output paths.
Usefsutil.ReadFileSafefor constrained file reads instead of reimplementing path-containment checks.
Do not manually register MCP or Copilot tool handlers; runnable Cobra commands are exposed through automatic generation inpkg/toolgen.
Use a typedexperimentalfield inksail.yamlfor configuration-gated behavior that is not an entire command; regenerate the schema and CRD.
Graduate validated experimental features by deleting the singleGuardcall; do not retain unnecessary experimental scaffolding.
Run formatting and linting withgolangci-lint run --fixandgolangci-lint run --timeout 5m; validate withgo buildandgo test ./....
Files:
internal/ciharness/ci_workflow_test.go
**/*.{go,yaml,yml,md,mdx,ts,tsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
Generated files must not be hand-edited; run
make generateas the canonical regeneration command.
Files:
internal/ciharness/ci_workflow_test.go
**/*_test.go
📄 CodeRabbit inference engine (AGENTS.md)
Add regression tests for confident bug fixes and run flaky-test candidates repeatedly with
go test -run <T> -count=10 ./....
Files:
internal/ciharness/ci_workflow_test.go
🧠 Learnings (3)
📚 Learning: 2026-07-14T16:00:38.424Z
Learnt from: devantler
Repo: devantler-tech/ksail PR: 6121
File: .github/workflows/ci.yaml:936-940
Timestamp: 2026-07-14T16:00:38.424Z
Learning: In GitHub Actions workflow shell steps that use `set -euo pipefail`, avoid using external commands (e.g., `grep -q` or `grep ... | ...`) directly as the condition in an `if` statement when the goal is a negative assertion (e.g., “string/pattern is absent”). External-command exit-code behavior under `set -e` can cause non-semantic failures (command errors, pipe issues, SIGPIPE, etc.) to be treated the same as a legitimate “no match,” potentially failing open. Prefer the robust pattern: capture command output first (e.g., `output="$(cmd)"` so real command failures abort due to `set -e`), then perform the assertion with a bash builtin string test such as `if [[ "$output" == *pattern* ]]; then ...; fi` (and use the negation of that builtin check for “absent”).
Applied to files:
.github/workflows/ci.yaml
📚 Learning: 2026-07-26T05:03:23.451Z
Learnt from: devantler
Repo: devantler-tech/ksail PR: 6363
File: .github/workflows/ci.yaml:308-313
Timestamp: 2026-07-26T05:03:23.451Z
Learning: In devantler-tech/ksail, the centralized GitHub Actions job `🧹 Lint - mega-linter` already runs MegaLinter’s `actionlint` for PRs that only change workflow files. When adding/adjusting workflow jobs (path filters included), don’t add extra/duplicated `mega-linter-runner -f go` (or other MegaLinter invocations) to specialized “script-validation” jobs solely because their `paths`/path filters include `.github/workflows/*.yaml`—rely on the centralized `Lint - mega-linter` job for workflow/YAML actionlint validation. Keep the existing `ci.yaml` EKS smoke-script/path-filter behavior so that workflow changes (notably `system-test-eks.yaml`) still trigger the intended command-line contract tests via the configured filtering.
Applied to files:
.github/workflows/ci.yaml
📚 Learning: 2026-08-02T19:26:41.922Z
Learnt from: devantler
Repo: devantler-tech/ksail PR: 6434
File: pkg/cli/clusterapi/eks_create_identity_test.go:0-0
Timestamp: 2026-08-02T19:26:41.922Z
Learning: In Go tests using Testify v1.11.1, do not flag require.NoError(t, err) inside an Eventually condition solely because the condition may run in another goroutine: require calls t.Errorf before FailNow, and t.Errorf marks the outer test as failed. However, prefer decomposing Eventually conditions so service errors, missing resources, and state or phase mismatches are reported separately for clearer diagnostics.
Applied to files:
internal/ciharness/ci_workflow_test.go
Disabling cancellation is not sufficient on a shared group. A concurrency group holds one running plus one pending run, and a third arrival cancels the pending one — so with three merges in quick succession the second merge's checks are still lost. Key the group on github.run_id for main so nothing there queues behind or evicts anything else. Pull requests and merge-queue entries keep the ref-keyed group.
Valid, and it defeats acceptance criterion 1 of #6384 as I had implemented it — thank you. A concurrency group holds one running plus one pending run, and a third arrival cancels the pending one. So Adopted the run-unique key for group: "ci-ksail-${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.run_id || github.ref }}"
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}Nothing on
Test updated to pin the new mechanism ( |
Re-requesting at Current state at this head: 32 passing, 0 failing; the code-relevant checks ( The same correction has been applied to the two sibling PRs — devantler-tech/platform#2992 (reviewed clean, now in the merge queue) and devantler-tech/kyverno-policies#35. @coderabbitai review |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '=== Changed files and diff ==='
git diff --stat HEAD^ HEAD
git diff -- .github/workflows/ci.yaml internal/ciharness/ci_workflow_test.go
printf '%s\n' '=== Workflow outline ==='
ast-grep outline .github/workflows/ci.yaml --items all || true
printf '%s\n' '=== Test file outline ==='
ast-grep outline internal/ciharness/ci_workflow_test.go --items all || true
printf '%s\n' '=== Relevant workflow concurrency declarations ==='
rg -n -C 5 '(^concurrency:|cancel-in-progress:|^\s*group:|refs/heads/main|github\.run_id)' .github/workflows
printf '%s\n' '=== CI harness test context ==='
rg -n -C 12 'TestCIWorkflowKeepsDefaultBranchRunsAlive|TestNoDefaultBranchWorkflowCancelsRunsInProgress|cancel-in-progress|concurrency|github\.run_id' internal/ciharness/ci_workflow_test.goLength of output: 22292 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '=== ci.yaml concurrency block ==='
sed -n '1,48p' .github/workflows/ci.yaml
printf '%s\n' '=== Complete CI harness tests ==='
sed -n '1,150p' internal/ciharness/ci_workflow_test.go
printf '%s\n' '=== PR diff from merge base ==='
base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
git diff --unified=80 "$base"...HEAD -- .github/workflows/ci.yaml internal/ciharness/ci_workflow_test.go
printf '%s\n' '=== Workflows that push to main and their cancellation values ==='
python3 - <<'PY'
from pathlib import Path
import yaml
for path in sorted(Path(".github/workflows").glob("*.y*ml")):
data = yaml.safe_load(path.read_text()) or {}
trigger = data.get(True, data.get("on", {}))
push = trigger.get("push", {}) if isinstance(trigger, dict) else {}
branches = push.get("branches", []) if isinstance(push, dict) else []
if "main" in branches:
concurrency = data.get("concurrency", {})
print(f"{path}: group={concurrency.get('group')!r}; cancel-in-progress={concurrency.get('cancel-in-progress')!r}")
PYLength of output: 16461
The workflow change itself has the required behavior. The tests do not yet provide the stated future-regression protection. You are interacting with an AI system. |
Asserting github.run_id, refs/heads/main and github.ref as independent
substrings also passed for the inverted expression, which hands main the shared
ref key. Assert the bound conditional instead.
A cancel-in-progress of ${{ true }} is a string to YAML, so the literal check
passed over it while GitHub still cancels every run. Reject constant-true
expressions and expressions that cannot be branch-conditional.
Both valid, and both were real holes in the regression protection rather than style points. Fixed in 1. Inverted expression. Confirmed — asserting assert.Containsf(t, workflow.Concurrency.Group,
"github.ref == 'refs/heads/main' && github.run_id", ...)Ablated: flipping 2. Ablated: setting This is the second time review has caught that my fix was right while my proof was weaker than it looked — the first being the pending-run gap itself. Both are worth having.
|
Re-requesting at
State at this head: 33 passing, 0 failing, 0 unresolved threads. The code-relevant checks — The same change is now merged in three sibling repos: devantler-tech/kyverno-policies#35, devantler-tech/platform#2992 and devantler-tech/agent-plugins#113. @coderabbitai review |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '=== Changed files relative to merge base ==='
base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
git diff --stat "$base"...HEAD
git diff --unified=100 "$base"...HEAD -- .github/workflows/ci.yaml internal/ciharness/ci_workflow_test.go
printf '%s\n' '=== Workflow outline ==='
ast-grep outline .github/workflows/ci.yaml --items all || true
printf '%s\n' '=== Test file outline ==='
ast-grep outline internal/ciharness/ci_workflow_test.go --items all || true
printf '%s\n' '=== ci.yaml concurrency and triggers ==='
rg -n -C 10 '(^name:|^on:|^concurrency:|^\s*group:|cancel-in-progress:|github\.run_id|refs/heads/main)' .github/workflows/ci.yaml
printf '%s\n' '=== Complete CI workflow regression tests ==='
sed -n '1,260p' internal/ciharness/ci_workflow_test.go
printf '%s\n' '=== All workflow concurrency blocks and main push triggers ==='
python3 - <<'PY'
from pathlib import Path
import yaml
for path in sorted(Path(".github/workflows").glob("*.y*ml")):
data = yaml.safe_load(path.read_text()) or {}
trigger = data.get(True, data.get("on", {}))
push = trigger.get("push", {}) if isinstance(trigger, dict) else {}
branches = push.get("branches", []) if isinstance(push, dict) else []
concurrency = data.get("concurrency")
if "main" in branches or concurrency is not None:
print(f"\n{path}")
print(f" push branches: {branches!r}")
print(f" concurrency: {concurrency!r}")
PYLength of output: 21704 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '=== Workflow files ==='
fd -t f -e yaml -e yml .github/workflows | sort
printf '%s\n' '=== main push triggers and concurrency context ==='
rg -n -U -C 3 \
'(?m)^\s*push:\s*\n(?:^(?:\s{4,}.*|\s*)\n)*?^\s*branches:\s*\[[^]]*\bmain\b[^]]*\]|(?m)^\s*push:\s*\n(?:^(?:\s{4,}.*|\s*)\n)*?^\s*branches:\s*\n(?:^\s*-\s*main\s*$|^\s{8,}-\s*main\s*$)|(?m)^concurrency:|(?m)^\s+cancel-in-progress:' \
.github/workflows || true
printf '%s\n' '=== All cancel-in-progress declarations ==='
rg -n -C 4 '^\s*cancel-in-progress:' .github/workflows || true
printf '%s\n' '=== Existing tests for cancellation helper ==='
rg -n -C 5 'cancelsUnconditionally\s*\(' internal/ciharnessLength of output: 1418 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '=== Workflow files ==='
fd -t f -e yaml -e yml . .github/workflows | sort
printf '%s\n' '=== push-to-main trigger blocks ==='
rg -n -U -C 5 \
'(?m)^\s*push:\s*\n(?:^(?:\s{4,}.*|\s*)\n)*?^\s*branches:\s*\[[^]]*\bmain\b[^]]*\]|(?m)^\s*push:\s*\n(?:^(?:\s{4,}.*|\s*)\n)*?^\s*branches:\s*\n(?:^\s{6,}-\s*main\s*$|^\s{8,}-\s*main\s*$)' \
.github/workflows || true
printf '%s\n' '=== All cancel-in-progress declarations ==='
rg -n -C 5 '^\s*cancel-in-progress:' .github/workflows || true
printf '%s\n' '=== Cancellation helper call sites ==='
rg -n -C 5 'cancelsUnconditionally\s*\(' internal/ciharness || trueLength of output: 11329
P2 — Reject expressions that are true on The workflow has the required behavior. The guard does not yet prove that future ✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/ciharness/ci_workflow_test.go`:
- Line 103: Update the cancellation-expression validation around
cancelsUnconditionally to evaluate expressions with github.ref set to
refs/heads/main instead of rejecting or accepting based only on tokens; fail
closed for expressions that cannot be safely evaluated. Add regression cases
covering "${{ github.ref && true }}" and "${{ github.ref != 'refs/heads/main' ||
true }}" so both are recognized as unconditional cancellation on the main
branch.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: de4ec75e-d475-4250-bd53-02b6f6256209
📒 Files selected for processing (2)
.github/workflows/ci.yamlinternal/ciharness/ci_workflow_test.go
📜 Review details
⏰ Context from checks skipped due to timeout. (17)
- GitHub Check: 🧪 System Test (Docker) (Vanilla, Docker, true, --cni Calico --csi Disabled --load-balancer Dis...
- GitHub Check: 🧪 System Test (Docker) (Vanilla, Docker, true, --gitops-engine Flux --local-registry ghcr.io/d...
- GitHub Check: 🧪 System Test (Docker) (K3s, Docker, true, --gitops-engine Flux --local-registry ghcr.io/devan...
- GitHub Check: 🧪 System Test (Docker) (K3s, Docker, true, --cni Calico --csi Disabled --load-balancer Disable...
- GitHub Check: 🧪 System Test (Docker) (K3s, Docker, true, --gitops-engine ArgoCD --local-registry ghcr.io/dev...
- GitHub Check: 🧪 System Test (Docker) (Vanilla, Docker, true, --gitops-engine ArgoCD --local-registry ghcr.io...
- GitHub Check: 🧪 System Test (Docker) (K3s, Docker, true, --name system-test-cluster --cni Cilium --csi Enabl...
- GitHub Check: 🧪 System Test (Docker) (K3s, Docker, true)
- GitHub Check: 🧪 System Test (Docker) (KWOK, Docker, true, --gitops-engine Flux --local-registry ghcr.io/deva...
- GitHub Check: 🧪 System Test (Docker) (Vanilla, Docker, true, --name system-test-cluster --cni Cilium --csi E...
- GitHub Check: 🧪 System Test (Docker) (KWOK, Docker, true, --cni Calico --csi Disabled --load-balancer Disabl...
- GitHub Check: 🧪 System Test (Docker) (KWOK, Docker, true)
- GitHub Check: 🧪 System Test (Docker) (KWOK, Docker, true, --name system-test-cluster --cni Cilium --csi Enab...
- GitHub Check: 🧪 System Test (Docker) (Vanilla, Docker, true)
- GitHub Check: 🧪 System Test (Docker) (KWOK, Docker, true, --gitops-engine ArgoCD --local-registry ghcr.io/de...
- GitHub Check: 📊 Code Coverage
- GitHub Check: Analyze (go)
🧰 Additional context used
📓 Path-based instructions (4)
.github/workflows/*.yaml
📄 CodeRabbit inference engine (AGENTS.md)
Validate workflow changes with
mega-linter-runner -f go; MegaLinter runsactionlintfor GitHub Actions workflows.
Files:
.github/workflows/ci.yaml
**/*.go
📄 CodeRabbit inference engine (AGENTS.md)
**/*.go: Use Go 1.26.1 or newer, matching the version declared ingo.mod.
All user-supplied file path arguments in CLI commands must be canonicalized withfsutil.EvalCanonicalPathbefore use; create parent directories first for new output paths.
Usefsutil.ReadFileSafefor constrained file reads instead of reimplementing path-containment checks.
Do not manually register MCP or Copilot tool handlers; runnable Cobra commands are exposed through automatic generation inpkg/toolgen.
Use a typedexperimentalfield inksail.yamlfor configuration-gated behavior that is not an entire command; regenerate the schema and CRD.
Graduate validated experimental features by deleting the singleGuardcall; do not retain unnecessary experimental scaffolding.
Run formatting and linting withgolangci-lint run --fixandgolangci-lint run --timeout 5m; validate withgo buildandgo test ./....
Files:
internal/ciharness/ci_workflow_test.go
**/*.{go,yaml,yml,md,mdx,ts,tsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
Generated files must not be hand-edited; run
make generateas the canonical regeneration command.
Files:
internal/ciharness/ci_workflow_test.go
**/*_test.go
📄 CodeRabbit inference engine (AGENTS.md)
Add regression tests for confident bug fixes and run flaky-test candidates repeatedly with
go test -run <T> -count=10 ./....
Files:
internal/ciharness/ci_workflow_test.go
🧠 Learnings (3)
📚 Learning: 2026-07-14T16:00:38.424Z
Learnt from: devantler
Repo: devantler-tech/ksail PR: 6121
File: .github/workflows/ci.yaml:936-940
Timestamp: 2026-07-14T16:00:38.424Z
Learning: In GitHub Actions workflow shell steps that use `set -euo pipefail`, avoid using external commands (e.g., `grep -q` or `grep ... | ...`) directly as the condition in an `if` statement when the goal is a negative assertion (e.g., “string/pattern is absent”). External-command exit-code behavior under `set -e` can cause non-semantic failures (command errors, pipe issues, SIGPIPE, etc.) to be treated the same as a legitimate “no match,” potentially failing open. Prefer the robust pattern: capture command output first (e.g., `output="$(cmd)"` so real command failures abort due to `set -e`), then perform the assertion with a bash builtin string test such as `if [[ "$output" == *pattern* ]]; then ...; fi` (and use the negation of that builtin check for “absent”).
Applied to files:
.github/workflows/ci.yaml
📚 Learning: 2026-07-26T05:03:23.451Z
Learnt from: devantler
Repo: devantler-tech/ksail PR: 6363
File: .github/workflows/ci.yaml:308-313
Timestamp: 2026-07-26T05:03:23.451Z
Learning: In devantler-tech/ksail, the centralized GitHub Actions job `🧹 Lint - mega-linter` already runs MegaLinter’s `actionlint` for PRs that only change workflow files. When adding/adjusting workflow jobs (path filters included), don’t add extra/duplicated `mega-linter-runner -f go` (or other MegaLinter invocations) to specialized “script-validation” jobs solely because their `paths`/path filters include `.github/workflows/*.yaml`—rely on the centralized `Lint - mega-linter` job for workflow/YAML actionlint validation. Keep the existing `ci.yaml` EKS smoke-script/path-filter behavior so that workflow changes (notably `system-test-eks.yaml`) still trigger the intended command-line contract tests via the configured filtering.
Applied to files:
.github/workflows/ci.yaml
📚 Learning: 2026-08-02T19:26:41.922Z
Learnt from: devantler
Repo: devantler-tech/ksail PR: 6434
File: pkg/cli/clusterapi/eks_create_identity_test.go:0-0
Timestamp: 2026-08-02T19:26:41.922Z
Learning: In Go tests using Testify v1.11.1, do not flag require.NoError(t, err) inside an Eventually condition solely because the condition may run in another goroutine: require calls t.Errorf before FailNow, and t.Errorf marks the outer test as failed. However, prefer decomposing Eventually conditions so service errors, missing resources, and state or phase mismatches are reported separately for clearer diagnostics.
Applied to files:
internal/ciharness/ci_workflow_test.go
🔇 Additional comments (2)
.github/workflows/ci.yaml (1)
28-36: LGTM!internal/ciharness/ci_workflow_test.go (1)
8-8: LGTM!Also applies to: 45-60
…ng tokens
Checking that an expression mentions github.ref does not establish what it
evaluates to on main. Both ${{ github.ref && true }} and
${{ github.ref != 'refs/heads/main' || true }} name the token and read as
branch-conditional, yet each is true on refs/heads/main and cancels the previous
run.
Accept only an absent value, an explicit false, or the exact approved
expression. Evaluating GitHub expressions here would mean shipping an
interpreter that itself needs testing; an allowlist makes each new form a
deliberate addition. Table test covers both counter-examples.
Valid — and this is the third time the fix held while my proof did not, so I've stopped trying to be clever about it. Fixed in Both counter-examples are real: Of your two suggested remedies I took the allowlist, not the evaluator: const approvedCancelExpression = "${{ github.ref != 'refs/heads/main' }}"
func mayCancelDefaultBranchRuns(value any) bool {
if value == nil { return false }
switch strings.TrimSpace(fmt.Sprintf("%v", value)) {
case "false", approvedCancelExpression: return false
default: return true
}
}Evaluating GitHub expressions in a test would mean shipping an interpreter that itself needs testing and can drift from GitHub's semantics — a second thing to get wrong. Failing closed makes any new form a deliberate addition whose behaviour on Regression cases added for both of your forms, plus
|
Re-requesting at
I took the allowlist rather than the evaluator deliberately: evaluating GitHub expressions in a test means shipping an interpreter that itself needs testing and can drift from GitHub's semantics. State at this head: 33 passing, 0 failing, 0 unresolved threads. @coderabbitai review |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
printf '%s\n' '=== Changed files ==='
git diff --stat "$base"...HEAD
git diff --unified=80 "$base"...HEAD -- .github/workflows/ci.yaml internal/ciharness/ci_workflow_test.go
printf '%s\n' '=== Workflow structure ==='
ast-grep outline .github/workflows/ci.yaml --items all || true
printf '%s\n' '=== Test structure ==='
ast-grep outline internal/ciharness/ci_workflow_test.go --items all || true
printf '%s\n' '=== Workflow concurrency declarations ==='
rg -n -C 8 '(^name:|^on:|^concurrency:|^\s*group:|^\s*cancel-in-progress:|github\.run_id|refs/heads/main)' .github/workflows
printf '%s\n' '=== Relevant test implementation ==='
rg -n -C 20 'TestCIWorkflowKeepsDefaultBranchRunsAlive|TestNoDefaultBranchWorkflowCancelsRunsInProgress|mayCancelDefaultBranchRuns|approvedCancelExpression' internal/ciharness/ci_workflow_test.go
printf '%s\n' '=== All main-push workflows and parsed concurrency ==='
python3 - <<'PY'
from pathlib import Path
import yaml
for path in sorted(Path(".github/workflows").glob("*.y*ml")):
data = yaml.safe_load(path.read_text()) or {}
trigger = data.get(True, data.get("on", {}))
push = trigger.get("push", {}) if isinstance(trigger, dict) else {}
branches = push.get("branches", []) if isinstance(push, dict) else []
if "main" in branches:
print(f"{path}: concurrency={data.get('concurrency')!r}")
PYLength of output: 53475 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '=== CI harness files and helper definitions ==='
fd -t f . internal/ciharness | sort
rg -n -C 12 'func readRepoFile\b|package ciharness' internal/ciharness
printf '%s\n' '=== Workflow push trigger blocks ==='
rg -n -U -C 4 \
'(?m)^on:\n(?:(?:^[[:blank:]].*\n)|(?:^\n))*?^[[:blank:]]+push:(?:\n(?:^[[:blank:]].*\n)*)?' \
.github/workflows || true
printf '%s\n' '=== Explicit main branch declarations ==='
rg -n -C 5 '^[[:blank:]]*branches:[[:blank:]]*(\[.*\bmain\b.*\]|$)|^[[:blank:]]*-[[:blank:]]*main[[:blank:]]*$' .github/workflows
printf '%s\n' '=== All workflow-level concurrency declarations ==='
rg -n -U -C 3 '(?m)^concurrency:\n(?:(?:^[[:blank:]].*\n)|(?:^\n))*?(?=^[^[:blank:]]|\z)' .github/workflows || true
printf '%s\n' '=== ci.yaml cancellation value and test allowlist references ==='
rg -n -C 3 'cancel-in-progress:|approvedCancelExpression|mayCancelDefaultBranchRuns' \
.github/workflows/ci.yaml internal/ciharness/ci_workflow_test.goLength of output: 19467
The fail-closed allowlist closes the expression bypass. The workflow keeps a run-unique group for You are interacting with an AI system. |
Advancing to the next lane. CodeRabbit answered this head at 03:46:56Z with a substantive verdict — "No additional findings. The fail-closed allowlist closes the expression bypass. The workflow keeps a run-unique group for On the four reviews it completed normally this hour, the qualifying artifact followed the reply within about two minutes. A reply on its own is explicitly not a green, so this is recorded as @codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Codex declined at 03:52:58Z: "You have reached your Codex usage limits for code reviews." Quota state with no retry window, so the lane cannot deliver at this head. Recorded as This is direct evidence for this pull request, not inherited from the same message seen on devantler-tech/agent-skills#85 half an hour ago. Lane state at |
|
@cursor review |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_9ec079c0-c498-4a44-a6fa-9196f1e026b8) |
The workflow-specific test still matched cancel-in-progress by substring, which
is misleading for the same reason it was wrong in the repo-wide test:
${{ github.ref != 'refs/heads/main' || true }} contains github.ref,
refs/heads/main and != while still cancelling on main. Both tests now compare
against the approved expression.
New head This commit came from my own review of the diff rather than from a reported finding: For context on the lane state at the previous head @coderabbitai review |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
printf '%s\n' '=== Changed files and merge-base diff ==='
git diff --stat "$base"...HEAD
git diff --unified=100 "$base"...HEAD -- .github/workflows/ci.yaml internal/ciharness/ci_workflow_test.go
printf '%s\n' '=== Go test structure ==='
ast-grep outline internal/ciharness/ci_workflow_test.go --items all || true
printf '%s\n' '=== Relevant Go test implementation ==='
rg -n -C 30 \
'TestCIWorkflowKeepsDefaultBranchRunsAlive|TestNoDefaultBranchWorkflowCancelsRunsInProgress|approvedCancelExpression|mayCancelDefaultBranchRuns|cancel-in-progress' \
internal/ciharness/ci_workflow_test.go
printf '%s\n' '=== CI workflow concurrency block ==='
rg -n -C 12 \
'^(name:|on:|concurrency:)|^\s+(group|cancel-in-progress):|github\.run_id|refs/heads/main' \
.github/workflows/ci.yaml
printf '%s\n' '=== All workflow-level concurrency values ==='
rg -n -U -C 5 \
'(?m)^concurrency:\n(?:(?:^[[:blank:]].*\n)|(?:^\n))*?(?=^[^[:blank:]]|\z)' \
.github/workflows || true
printf '%s\n' '=== Main branch push declarations ==='
rg -n -C 4 \
'^[[:blank:]]*branches:[[:blank:]]*(\[.*\bmain\b.*\]|$)|^[[:blank:]]*-[[:blank:]]*main[[:blank:]]*$' \
.github/workflowsLength of output: 28278
P2 — Verify the non- For example, this expression passes both checks: group: "ci-ksail-${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.run_id || github.ref && github.run_id }}"It gives Assert the complete approved group expression, or separately assert the ✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/ciharness/ci_workflow_test.go`:
- Around line 62-71: Update the concurrency-group assertions in the relevant
test to validate the complete approved group expression, or require its fallback
operand to be exactly github.ref rather than relying on substring checks. Add a
regression case covering a group expression with github.run_id as the fallback,
ensuring it is rejected while preserving the existing valid expression checks.
- Around line 99-103: Update the workflow-selection logic surrounding
mayCancelDefaultBranchRuns and the checked >= 2 guard to identify every workflow
that can run on main, including unfiltered push triggers, branches-ignore
filters, and branch glob patterns matching main. Ensure only applicable
workflows are asserted, and add regression cases covering unfiltered push and
branches-ignore triggers.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 2793935f-f12d-4742-ad33-27ca3b112d6e
📒 Files selected for processing (1)
internal/ciharness/ci_workflow_test.go
📜 Review details
⏰ Context from checks skipped due to timeout. (13)
- GitHub Check: ⛵ Operator Chart E2E
- GitHub Check: 🧩 Verify Desktop Module Tidy
- GitHub Check: 🏠 Home Isolation Guard
- GitHub Check: 🏗️ Build KSail Binary
- GitHub Check: 🛡️ Vulnerability Scan
- GitHub Check: 📦 Tidy
- GitHub Check: 🧹 Lint - golangci-lint
- GitHub Check: 🧹 Lint - mega-linter
- GitHub Check: 📊 Code Coverage
- GitHub Check: 🔍 Dead Code Analysis
- GitHub Check: 🏗️ Build
- GitHub Check: 🧪 Test
- GitHub Check: Analyze (go)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.go
📄 CodeRabbit inference engine (AGENTS.md)
**/*.go: Use Go 1.26.1 or newer, matching the version declared ingo.mod.
All user-supplied file path arguments in CLI commands must be canonicalized withfsutil.EvalCanonicalPathbefore use; create parent directories first for new output paths.
Usefsutil.ReadFileSafefor constrained file reads instead of reimplementing path-containment checks.
Do not manually register MCP or Copilot tool handlers; runnable Cobra commands are exposed through automatic generation inpkg/toolgen.
Use a typedexperimentalfield inksail.yamlfor configuration-gated behavior that is not an entire command; regenerate the schema and CRD.
Graduate validated experimental features by deleting the singleGuardcall; do not retain unnecessary experimental scaffolding.
Run formatting and linting withgolangci-lint run --fixandgolangci-lint run --timeout 5m; validate withgo buildandgo test ./....
Files:
internal/ciharness/ci_workflow_test.go
**/*.{go,yaml,yml,md,mdx,ts,tsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
Generated files must not be hand-edited; run
make generateas the canonical regeneration command.
Files:
internal/ciharness/ci_workflow_test.go
**/*_test.go
📄 CodeRabbit inference engine (AGENTS.md)
Add regression tests for confident bug fixes and run flaky-test candidates repeatedly with
go test -run <T> -count=10 ./....
Files:
internal/ciharness/ci_workflow_test.go
🧠 Learnings (1)
📚 Learning: 2026-08-02T19:26:41.922Z
Learnt from: devantler
Repo: devantler-tech/ksail PR: 6434
File: pkg/cli/clusterapi/eks_create_identity_test.go:0-0
Timestamp: 2026-08-02T19:26:41.922Z
Learning: In Go tests using Testify v1.11.1, do not flag require.NoError(t, err) inside an Eventually condition solely because the condition may run in another goroutine: require calls t.Errorf before FailNow, and t.Errorf marks the outer test as failed. However, prefer decomposing Eventually conditions so service errors, missing resources, and state or phase mismatches are reported separately for clearer diagnostics.
Applied to files:
internal/ciharness/ci_workflow_test.go
🔇 Additional comments (1)
internal/ciharness/ci_workflow_test.go (1)
120-147: LGTM!Also applies to: 149-186
…hing trigger
Two partial checks remained. The group was matched by substring, so
${{ ... && github.run_id || github.run_id }} passed while giving pull-request
and merge-queue runs unique groups, which stops superseded runs there cancelling
at all. And workflow selection matched only a literal 'main', skipping
unfiltered pushes, branches-ignore and globs.
Assert the group against an approved expression, and select workflows with a
matcher covering unfiltered push, ignore filters and glob patterns. This adds
copilot-setup-steps.yml to the checked set — it reaches main through an
unfiltered push and was never checked before.
Both valid, both fixed in 1. Group matched by substring. Confirmed: 2. Selection missed triggers that reach The concrete consequence: the checked set went from 5 workflows to 6. I found this while re-running the guard-the-guard ablation: after neutering all five literal- Also worth noting what I did not do: I had spotted the unfiltered-push gap myself earlier in this PR and chose to document it as a limitation rather than close it. That was the wrong call and your finding is the correction.
|
matchesDefaultBranch skipped any pattern filepath.Match rejected, so a pattern GitHub accepts and Go does not would silently drop the workflow from the checked set — the hole this matcher exists to close. The safe answer differs by caller, because branches-ignore inverts the result: an inclusion list must assume an unreadable pattern matches, an exclusion list must assume it does not. Either way the workflow stays checked.
New head
@coderabbitai review |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
printf '%s\n' '=== Current revision ==='
git rev-parse HEAD
printf '%s\n' '=== Merge-base diff ==='
git diff --stat "$base"...HEAD
git diff --unified=100 "$base"...HEAD -- .github/workflows/ci.yaml internal/ciharness/ci_workflow_test.go
printf '%s\n' '=== Go test file outline ==='
ast-grep outline internal/ciharness/ci_workflow_test.go --items all || true
printf '%s\n' '=== Default-branch matcher implementation and call sites ==='
rg -n -C 35 \
'func (matchesDefaultBranch|runsOnDefaultBranch|mayCancelDefaultBranchRuns)|matchesDefaultBranch\s*\(|runsOnDefaultBranch\s*\(|TestRunsOnDefaultBranch|approved(Group|Cancel)Expression' \
internal/ciharness
printf '%s\n' '=== Workflow trigger and concurrency declarations ==='
rg -n -U -C 8 \
'(?m)^on:\n(?:(?:^[[:blank:]].*\n)|(?:^\n))*?(?=^[^[:blank:]]|\z)|(?m)^concurrency:\n(?:(?:^[[:blank:]].*\n)|(?:^\n))*?(?=^[^[:blank:]]|\z)' \
.github/workflows || true
printf '%s\n' '=== All branch filter fields ==='
rg -n -C 4 '^[[:blank:]]*(branches|branches-ignore|tags|tags-ignore):|^[[:blank:]]*-[[:blank:]]*.+$' \
.github/workflows || trueLength of output: 200654
The exact-expression assertions protect the [approve] You are interacting with an AI system. |
Differential finding on I built the same two fixes independently on this branch and lost the push race, so I ran my ablations against the merged-in version instead. Both of CodeRabbit's findings are genuinely closed by it — verified, not assumed:
The one place my version and this one disagree is worth a line. Probed both matchers over patterns GitHub does match against
So a workflow filtered on Contrived branch names, so this is low severity and not a merge blocker. But the fix is one line — treat a pattern containing if pattern == "main" || strings.ContainsAny(pattern, "*?+") {
return true // for the inclusion arm; the ignore arm keeps its inverted sense
}That also restores the symmetry the two call sites rely on: Reasoned from a probe run against this branch's code, not from the diff. |
devantler
left a comment
There was a problem hiding this comment.
🤖 Generated by the Agentic Engineer
Self-review (fallback — CodeRabbit, Codex and Cursor Bugbot unavailable)
Reviewed commit: ea76b9ae38d9d364cd307982215ab1cedc11d6d4
Per-lane state at this head, each tried in priority order with evidence from this pull request:
- CodeRabbit — requested 04:09Z. Answered 04:09:52Z with a substantive verdict ("No additional findings", plus
[approve]), but as a command reply. Thirteen minutes later: 0 review objects at this head, no post-requestsummarizecomment, and the head's status unchanged since 04:09:08Z, still the not-run defaultReview skipped: automatic reviews are disabled. Its four completed reviews this hour produced the qualifying artifact within ~2 minutes. Recordedcr:no-gate. - Codex — requested 03:5xZ on this PR. Declined 03:52:58Z: "You have reached your Codex usage limits for code reviews." Recorded
codex:no-gate. - Cursor Bugbot — requested 03:5xZ on this PR with a bare trigger. Check-run
Cursor Bugbotreturnedconclusion: neutralwithoutput.title: Errorand summary "Bugbot run failed";cursor[bot]commented "Bugbot couldn't run - usage limit reached" at 03:53:46Z. Run-never-started shape, not findings.
Review
Workflow change. On main the group resolves to ci-ksail-<workflow>-<run_id>, unique per run, so nothing there queues behind or evicts anything else — which is the property cancel-in-progress: false alone cannot provide, since a group holds one running plus one pending run and a third arrival cancels the pending one. Off main the group is ref-keyed and cancellation stays enabled, so superseded pull-request runs still cancel; that half was observed live on this PR (push 33869dbf cancelled, c523310967b3 proceeded). github.run_id is always a nonzero integer, so the || fallback is unreachable when the condition holds. merge_group entries carry their own ref and are unaffected — and this repository has no merge queue configured (rulesets carry pull_request, non_fast_forward, code_quality, code_scanning; classic protection 404s).
Test harness. Both the group and the cancellation are compared against exact approved expressions rather than matched by substring. That is deliberate and is the whole lesson of this PR's review history: github.ref != 'refs/heads/main' && github.run_id || github.ref inverts the condition, ... && github.run_id || github.run_id breaks pull-request cancellation via the fallback, and ${{ github.ref && true }} / ${{ github.ref != 'refs/heads/main' || true }} both evaluate true on main — every one of them satisfies a token check. Evaluating GitHub expressions in a test would mean shipping an interpreter that itself needs testing, so an allowlist is the honest guard.
runsOnDefaultBranch selects workflows by what their triggers admit rather than by a literal branch name, covering an unfiltered push:, branches-ignore, and glob patterns. It fails open toward inclusion, and the unknown-pattern answer is parameterised per caller because branches-ignore negates the result — failing open on both sides would skip the workflow. Twenty-two table cases across the two helpers pin these.
Verdict: no P0/P1 findings.
Two nits, recorded rather than fixed, as appropriate for a repository-local guard: the default branch name main and the ci-ksail- group prefix are hardcoded, so a rename would need this file updated — which the exact-match assertions make loud rather than silent.
Limitation carried forward
The default-branch behaviour — two closely-spaced merges to main both retaining a completed 🧪 Test — is observable only after this lands and is not claimed here. CI is still settling at this head (32 passing, 0 failing, 4 System Test entries outstanding); promotion waits on that.
ReadinessProgrammatically tested. CI fully settled at Reviewed. All three lanes were tried at this head with per-PR evidence and none delivered: CodeRabbit answered with a clean verdict as a command reply but produced no review object, no Tried and evaluated as a user. The pull-request half was observed live on this PR rather than reasoned: two pushes 73 seconds apart produced What is not claimed. The default-branch half — two closely-spaced merges to Promoting on that basis. |

Why
maincan report green on tests that never ran. Every merge tomainshares one concurrency key, so a merge landing while the previous merge's checks are still running cancels them — and a cancelled check is not a failed check, so the commit status settles green anyway.That is how a broken Go test suite reached
mainunnoticed and then surfaced as an unrelated PR author's problem days later.What
Runs on
mainare now allowed to finish. Superseded pull-request and merge-queue runs still cancel exactly as before, so the fast-feedback behaviour developers rely on is unchanged — only the default branch, where each commit's run is its own verification record, stops being evicted.Also adds a guard so no future workflow can reintroduce the same eviction on
main.Fixes #6384
Part of #6373