Skip to content
Merged
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
47 changes: 46 additions & 1 deletion .github/workflows/apply.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ on:
description: "One-time: import ID for the resource above (e.g. the repo name for github_repository)."
required: false
default: ""
state_rm_addresses:
description: "One-time: comma-separated Terraform resource addresses to remove from state (e.g. for a member who left the org and whose resource can no longer be refreshed). Leave empty for a normal apply."
required: false
default: ""
schedule:
- cron: "17 */4 * * *"
push:
Expand All @@ -21,8 +25,24 @@ on:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
# This workflow has no pull_request trigger, so github.event.pull_request.number
# is always null here -- the group key collapsed to just github.ref in
# practice, which only serializes runs against the same ref. A manual
# workflow_dispatch run (e.g. one-time state rm) dispatched against a
# non-default ref could then run concurrently with a scheduled/push apply
# against main, racing on the same remote state. Use a single, unqualified
# group so every run of this workflow -- manual or automatic, any ref --
# is always serialized against every other.
group: ${{ github.workflow }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
cancel-in-progress: false
# Default queue behavior only keeps the single most-recently-queued run
# pending in a group -- an older pending run gets canceled and replaced.
# A manual one-time recovery run (state rm, or the exclude_addresses
# escape hatch) dispatched while a scheduled/push run is in progress
# could get silently dropped and replaced by the next automatic trigger
# before it ever executes. queue: max keeps every pending run queued
# (up to GitHub's cap of 100) instead of dropping older ones.
queue: max
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment on lines +28 to +45

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- workflow files ---'
git ls-files '.github/workflows/*'
printf '%s\n' '--- apply.yaml structure ---'
wc -l .github/workflows/apply.yaml
sed -n '1,150p' .github/workflows/apply.yaml
printf '%s\n' '--- queue/concurrency references ---'
rg -n --hidden -S 'queue:|concurrency:|cancel-in-progress|workflow_dispatch|schedule:|push:' .github/workflows .github 2>/dev/null || true

Repository: osac-project/github-config

Length of output: 7096


🌐 Web query:

GitHub Actions concurrency workflow syntax queue max supported key pending runs group cancel-in-progress

💡 Result:

GitHub Actions provides the concurrency keyword to manage the execution of workflows and jobs within a shared concurrency group. By default, GitHub Actions ensures that only one job or workflow run can be active (in-progress) and at most one can be in a pending state within a concurrency group [1][2][3]. Key configuration options include: - group: A string or dynamic expression that defines the identifier for the concurrency group. Workflows or jobs sharing the same group name are subject to concurrency constraints [2][4]. - cancel-in-progress: A boolean or conditional expression. When set to true, any currently running job or workflow in the group will be canceled when a new run is triggered. By default (or when set to false), only pending jobs are canceled when a new job is queued [1][3]. - queue: This property enables advanced queuing for pending jobs. - single (default): Allows at most one pending job. If a new job is queued, any existing pending job is canceled and replaced by the new one [1][2]. - max: Allows up to 100 jobs to be queued in a pending state. When the queue reaches this limit, additional triggered jobs are canceled [1][5][6]. Important constraints: - Incompatibility: The queue: max property cannot be used in conjunction with cancel-in-progress: true because they define conflicting behaviors for managing concurrent runs [2][3]. - Queue Limit: When using queue: max, the concurrency group supports up to 100 queued runs; any runs triggered beyond this capacity will be rejected [6]. - Uniqueness: Concurrency group names are scoped to the repository. If multiple workflows share the same group name, they will all be subject to the same concurrency limits [4][3].

Citations:


🌐 Web query:

site:github.com/actions/checkout README ref SHA defaults triggering event checkout commit

💡 Result:

When using the GitHub Actions actions/checkout action, the ref input defaults to the specific reference (branch, tag) or commit SHA that triggered the workflow run [1][2]. If the action is checking out a repository other than the one that triggered the workflow, it defaults to the repository's default branch [1][2]. By default, the action fetches only a single commit (fetch-depth: 1) corresponding to that ref or SHA [1][3]. Note that for certain event types, such as pull_request, the workflow might be triggered by a specific commit, and the checkout action will default to that SHA in a detached HEAD state [1][4]. Users often explicitly set the ref to github.head_ref or github.event.pull_request.head.sha when working with pull request events to ensure the desired branch or commit is checked out correctly [1][5].

Citations:


🌐 Web query:

site:docs.github.com GitHub Actions concurrency ordering is not guaranteed queue order

💡 Result:

According to official GitHub Actions documentation, jobs or workflow runs within the same concurrency group are processed in a first-in-first-out (FIFO) order, specifically based on the time each run started waiting on the concurrency group rather than the time the workflow was originally dispatched [1][2]. However, GitHub explicitly notes that ordering is not guaranteed [1][3]. This is because the actual start time of a job or workflow run—which determines its position in the queue—can vary due to system factors, meaning the sequence in which runs enter the "pending" state may not always strictly mirror the intended execution order [1][2]. When using concurrency controls: - By default, only one pending run is allowed in a group; new runs will cancel existing pending runs [1][4]. - You can enable queuing by using the queue property (e.g., queue: max), which allows multiple runs to wait in the concurrency group [1][2]. - Concurrency group names are case-insensitive, and using the same name across different workflows will cause them to share the same concurrency queue [1][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
from itertools import permutations
import re

path = Path(".github/workflows/apply.yaml")
text = path.read_text()

assert re.search(r"(?m)^  group:\s*\$\{\{\s*github\.workflow\s*\}\}\s*$", text)
assert re.search(r"(?m)^  cancel-in-progress:\s*false\s*$", text)
assert re.search(r"(?m)^  queue:\s*max\s*$", text)
assert re.search(r"(?m)^      - uses:\s*actions/checkout@[^ ]+\s*$", text)
assert re.search(r"(?m)^      - name:\s*TF Apply\s*$", text)

events = [
    ("push", "commit-A"),
    ("push", "commit-B"),
    ("push", "commit-C"),
]
# actions/checkout defaults to the triggering ref/SHA, so each queued run
# applies the commit attached to its event.
queued = [(event, sha) for event, sha in events]
orders = list(permutations(queued))
stale_order = next(order for order in orders if [sha for _, sha in order] !=
                   [sha for _, sha in queued])

print("shared concurrency group: Apply configuration")
print("pending-run capacity: 100")
print("checkout/apply revisions in trigger order:", [sha for _, sha in queued])
print("possible execution order:", [sha for _, sha in stale_order])
print("stale revision can execute after a newer revision:",
      stale_order.index(("push", "commit-A")) >
      stale_order.index(("push", "commit-B")))
PY

Repository: osac-project/github-config

Length of output: 436


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from itertools import permutations

revisions = ["commit-A", "commit-B", "commit-C"]
for order in permutations(revisions):
    if any(order.index(older) > order.index(newer)
           for older, newer in zip(revisions, revisions[1:])):
        print("trigger order:", revisions)
        print("possible execution order:", list(order))
        print("older revision executes after newer revision: true")
        break
else:
    raise SystemExit("No stale execution order found")
PY

Repository: osac-project/github-config

Length of output: 330


Skip superseded automatic applies before TF Apply.

queue: max keeps up to 100 push and schedule runs pending with manual recovery runs. actions/checkout uses each triggering revision, and concurrency order is not guaranteed. An older commit can therefore apply after a newer commit and roll infrastructure back. Skip superseded automatic runs, or isolate manual recovery while retaining one shared state lock.

🧰 Tools
🪛 actionlint (1.7.12)

[error] 45-45: unexpected key "queue" for "concurrency" section. expected one of "cancel-in-progress", "group"

(syntax-check)

🤖 Prompt for 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.

In @.github/workflows/apply.yaml around lines 28 - 45, Update the apply workflow
before the TF Apply step to skip queued automatic push or schedule runs that
have been superseded by a newer revision, while allowing manual recovery runs to
execute. Preserve serialization through the shared concurrency group for all
runs, and ensure the supersession check does not discard workflow_dispatch
recovery operations.


jobs:
apply:
Expand Down Expand Up @@ -64,6 +84,31 @@ jobs:
tofu import "${{ inputs.import_address }}" "${{ inputs.import_id }}"
env:
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
# Handles a resource whose live state can no longer be refreshed (e.g.
# a github_membership/github_team_membership for a user who left the
# org, which errors on read instead of just reporting "gone") --
# refresh failures like this abort the whole apply before anything
# else in the plan can land, atomically, even resources unrelated to
# the broken one. Manual, one-time use via workflow_dispatch input; a
# no-op (skipped entirely) for the normal scheduled/push triggers,
# which never set this input.
- name: TF State Remove (one-time, manual only)
if: inputs.state_rm_addresses != ''
run: |
if [[ "${STATE_RM_ADDRESSES}" == *$'\n'* ]]; then
echo "::error::state_rm_addresses must be comma-separated on a single line, not newline-separated." >&2
exit 1
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
IFS=',' read -ra ADDRS <<< "${STATE_RM_ADDRESSES}"
for addr in "${ADDRS[@]}"; do
if [[ -z "${addr}" || "${addr}" == -* ]]; then
echo "::error::invalid resource address '${addr}' -- addresses must be non-empty and cannot start with '-' (tofu would parse it as an option)." >&2
exit 1
fi
done
tofu state rm "${ADDRS[@]}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
env:
STATE_RM_ADDRESSES: ${{ inputs.state_rm_addresses }}
- name: TF Apply
run: |
tofu apply -concise -auto-approve
Expand Down
Loading