From 9aa96779e120cd2b35a88b68c082f9e428eeb008 Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Fri, 26 Jun 2026 11:35:37 +0000 Subject: [PATCH 1/3] Add concurrency control and job timeouts in CI - Cancel superseded CI runs on the same PR (keyed by PR number); push/tag/ release runs get unique groups via run_id so the release pipeline's workflow_call is never interrupted. - Add timeout-minutes to every CI job to replace the implicit 6h default. - Group Dependabot minor/patch updates per ecosystem (gomod, github-actions); major updates still open individual PRs. Co-Authored-By: Claude Opus 4.8 --- .github/dependabot.yml | 14 ++++++++++++++ .github/workflows/ci.yml | 13 +++++++++++++ 2 files changed, 27 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7c355809..fbfdabeb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,6 +10,13 @@ updates: labels: - "semver: patch" - "docs: skip" + groups: + go-dependencies: + patterns: + - "*" + update-types: + - "minor" + - "patch" - package-ecosystem: "github-actions" directory: "/" @@ -21,3 +28,10 @@ updates: labels: - "semver: patch" - "docs: skip" + groups: + github-actions: + patterns: + - "*" + update-types: + - "minor" + - "patch" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5611ecc4..51ed5977 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,10 +15,18 @@ permissions: pull-requests: write checks: write +# Cancel superseded runs on the same PR; never cancel push/tag/release runs +# (each gets a unique group via run_id) so reusable workflow_call from the +# release pipeline is never interrupted. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: lint: name: Lint runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Checkout code uses: actions/checkout@v7 @@ -36,6 +44,7 @@ jobs: goreleaser-check: name: GoReleaser Check runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Checkout code uses: actions/checkout@v7 @@ -50,6 +59,7 @@ jobs: test-unit: name: Unit Tests runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout code uses: actions/checkout@v7 @@ -83,6 +93,7 @@ jobs: test-integration: name: Integration Tests (${{ matrix.os }}${{ matrix.shard && format(' shard {0}/{1}', matrix.shard, matrix.shard_total) || '' }}) runs-on: ${{ matrix.os }} + timeout-minutes: 45 strategy: fail-fast: false matrix: @@ -186,6 +197,7 @@ jobs: runs-on: ubuntu-latest needs: test-integration if: always() + timeout-minutes: 5 steps: - name: Verify integration matrix succeeded run: | @@ -203,6 +215,7 @@ jobs: - test-unit - test-integration runs-on: ubuntu-latest + timeout-minutes: 30 permissions: contents: write steps: From f7e19e2955c09543bc20fbced7d5cdb5a0ce3178 Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Fri, 26 Jun 2026 12:09:44 +0000 Subject: [PATCH 2/3] test: fix flaky Windows temp-dir cleanup in az stop-interception test TestAzStopInterceptionNoOpWhenNotIntercepting is the only az test that runs the real az binary against a temp HOME on Windows. az spawns background processes that hold handles open under HOME\.azure, so t.TempDir()'s auto RemoveAll fails with "being used by another process" and fails an otherwise-passing test. Use a best-effort temp HOME (azTempHome) that ignores cleanup errors instead. Co-Authored-By: Claude Opus 4.8 --- test/integration/az_interception_test.go | 2 +- test/integration/setup_azure_test.go | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/test/integration/az_interception_test.go b/test/integration/az_interception_test.go index 2f433bb3..9ee73dfe 100644 --- a/test/integration/az_interception_test.go +++ b/test/integration/az_interception_test.go @@ -56,7 +56,7 @@ func TestAzStopInterceptionNoOpWhenNotIntercepting(t *testing.T) { requireAzCLI(t) t.Parallel() workDir := azureWorkDir(t) - home := t.TempDir() // fresh ~/.azure: active cloud is the default AzureCloud + home := azTempHome(t) // fresh ~/.azure: active cloud is the default AzureCloud stdout, _, err := runLstk(t, testContext(t), workDir, env.With(env.Home, home), diff --git a/test/integration/setup_azure_test.go b/test/integration/setup_azure_test.go index c414cd64..0edbd644 100644 --- a/test/integration/setup_azure_test.go +++ b/test/integration/setup_azure_test.go @@ -22,6 +22,19 @@ func requireAzCLI(t *testing.T) { } } +// azTempHome returns a temporary HOME directory whose cleanup tolerates Windows +// failures. The Azure CLI spawns background processes that keep handles open +// under HOME\.azure, so Go's t.TempDir() auto-cleanup can fail with "being used +// by another process" on Windows and wrongly fail an otherwise-passing test. +// We remove it best-effort instead. +func azTempHome(t *testing.T) string { + t.Helper() + dir, err := os.MkdirTemp("", "lstk-az-home-") + require.NoError(t, err) + t.Cleanup(func() { _ = os.RemoveAll(dir) }) + return dir +} + // azureWorkDir prepares a fresh workDir with a project-local `.lstk/config.toml` // containing an Azure container, and returns its path. Tests run `lstk` with // `cmd.Dir = workDir` so the project-local config search finds this file — From 087e2dd1b1c8bb6c7275ed209b69d41b42316cf0 Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Mon, 29 Jun 2026 16:37:19 +0300 Subject: [PATCH 3/3] ci: simplify cancel-in-progress to true The unique-group fallback (github.run_id for non-PR events) already guarantees push/tag/release runs are never cancelled, so the github.event_name conditional was redundant with cancel-in-progress: true. --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51ed5977..7cdd20f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,12 +15,12 @@ permissions: pull-requests: write checks: write -# Cancel superseded runs on the same PR; never cancel push/tag/release runs -# (each gets a unique group via run_id) so reusable workflow_call from the -# release pipeline is never interrupted. +# Cancel superseded runs on the same PR. Non-PR runs (push/tag/release) each +# get a unique group via run_id, so cancel-in-progress can never touch them — +# the reusable workflow_call from the release pipeline is never interrupted. concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} + cancel-in-progress: true jobs: lint: