From 29a25f7ee9fabd0e3882d94cba88b42a612ecbcf Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Fri, 26 Jun 2026 11:35:37 +0000 Subject: [PATCH 1/2] 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 29443f9515313feb628a4653926a39d050bfaca1 Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Fri, 26 Jun 2026 12:09:44 +0000 Subject: [PATCH 2/2] 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 3c7564bd..dc6b56cc 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 —