Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ jobs:
lint:
name: Lint
runs-on: ubuntu-latest
# Bound the job so a hung step (a network fetch that stalls without refusing
# the connection, a stuck pip/npx) fails fast instead of grinding to GitHub's
# 360-minute default — the long-tail behind the fleet failure-rate/p95
# warning (issue #986). See test/workflows/ci/install-resilience.bats.
timeout-minutes: 10
permissions:
contents: read
steps:
Expand Down Expand Up @@ -51,6 +56,8 @@ jobs:
document-start: disable
comments:
min-spaces-from-content: 1
indentation:
spaces: 2
YAMLLINTRC
) .github/workflows/ standards/workflows/ standards/dependabot/

Expand Down Expand Up @@ -81,6 +88,7 @@ jobs:
shellcheck:
name: ShellCheck
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
Expand All @@ -101,6 +109,7 @@ jobs:
agent-security:
name: Agent Security Scan
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
Expand Down Expand Up @@ -128,6 +137,7 @@ jobs:
secret-scan:
name: Secret scan (gitleaks)
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
Expand Down
63 changes: 63 additions & 0 deletions test/workflows/ci/install-resilience.bats
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,69 @@ step_block_with() {
[[ "$block" =~ npm_config_fetch_timeout:[[:space:]]*\'?[1-9] ]]
}

@test "install: every job in ci.yml declares a bounded timeout-minutes" {
# An unbounded job turns a single hung step (a network fetch that stalls
# without refusing the connection, a stuck npx/pip) into a run that grinds on
# to GitHub's 360-minute default before failing — the long-tail behind the
# fleet failure-rate/p95 warning (issue #986). Every job must therefore cap
# itself with a bounded timeout-minutes so a hang fails fast; this test asserts
# no job can silently drop that cap. Sibling fix: pr-auto-review-sweep.yml
# (#947) added timeout-minutes for the same reason.
[ -f "$TT_WORKFLOW" ]

# Walk the `jobs:` block. Job ids are the only keys indented exactly 2 spaces;
# each job's own keys (including timeout-minutes) sit at 4 spaces. For every
# job id we assert its block carries a `timeout-minutes: N` with 1 <= N <= 59
# (59 is GitHub's documented cap for setup-steps jobs and a sane fleet bound).
Comment thread
coderabbitai[bot] marked this conversation as resolved.
local in_jobs=0
local job_count=0
local current_job="" current_ok=0 line=""
finish_job() {
if [[ -n "$current_job" ]]; then
[ "$current_ok" -eq 1 ] || {
echo "job '$current_job' is missing a bounded timeout-minutes (1-59)" >&2
return 1
}
fi
return 0
}

while IFS= read -r line || [[ -n "$line" ]]; do
line="${line%$'\r'}"
# Enter the jobs: block (top-level key, no indentation).
if [[ "$line" =~ ^jobs:[[:space:]]*$ ]]; then
in_jobs=1
continue
fi
[ "$in_jobs" -eq 1 ] || continue
# A new top-level key (no leading space) ends the jobs: block.
if [[ "$line" =~ ^[^[:space:]] ]]; then
finish_job || return 1
break
fi
# A job id: exactly two spaces of indent then `name:`.
if [[ "$line" =~ ^\ \ ([A-Za-z0-9_-]+):[[:space:]]*$ ]]; then
finish_job || return 1
current_job="${BASH_REMATCH[1]}"
current_ok=0
job_count=$((job_count + 1))
continue
fi
# timeout-minutes for the current job (4-space indent), value 1-59.
if [[ "$line" =~ ^\ \ \ \ timeout-minutes:[[:space:]]*([0-9]+)[[:space:]]*$ ]]; then
local v="${BASH_REMATCH[1]}"
if [ "$v" -ge 1 ] && [ "$v" -le 59 ]; then
current_ok=1
fi
fi
done < "$TT_WORKFLOW"
# Handle the final job when the file ends inside the jobs: block.
finish_job || return 1

# Guard against a parser that silently matches nothing.
[ "$job_count" -ge 1 ]
}

@test "install: the gitleaks release download retries on transient failure" {
# `gh release download` pulls gitleaks from GitHub Releases (which redirects to
# objects.githubusercontent.com) and has no native retry — the exact failure
Expand Down
Loading