Mutable tags and the dependency-review v5 branch executed inside every consumer, including OIDC deploy jobs. SHA pins with version comments match the policy scanner and let Renovate advance them.
* ci: expand labeler globs and skip dependabot pr policy
.NET product paths never matched app, so backend PRs stayed unlabeled. Dependabot PRs still ran commit-subject and pin checks on generated titles. Skip those PRs in the reusable policy job.
* fix(labeler): match nested elastic beanstalk config paths
Root-only .ebextensions and .platform globs miss api/.ebextensions in monorepos. Mirror the Dockerfile nested form.
ci-python-app.yaml and ci-mobile-ios.yaml built their concurrency group
from ${{ github.job }}. In a called workflow that expression evaluates to
the caller's job id, not the job's own id, so every job in the reusable
resolved to the same group. With cancel-in-progress: true they cancelled
each other.
Observed in pr-reviewer after repinning it off a ref that predates the
concurrency blocks: one run, ci / lint cancelled 1s after start by a
sibling, ci / subproject-tests succeeded, and the aggregator failed on the
cancelled dependency.
Replaces the expression with the job id written out literally in all 7
groups, and records the reason at the first block in each file.
release.yaml documented a generic caller example but not the caller that
actually exists in this repo. Notes that release-on-merge.yaml computes the
version, calls this workflow by local path, and is the only caller that does
so without a version comment.
Callers pin `uses:` to a commit SHA of this repo. Dependabot's
github-actions updater finds a newer SHA for a pinned ref by reading the
target repo's tags and releases; this repo has 0 tags and 0 releases, so
there is nothing for it to resolve and it reports no update. The fleet's
pins have not moved as a result.
Adds a caller for the release reusable, triggered on push to main and
filtered to paths under .github/workflows/ excluding the three files no
caller consumes (ci.yaml, labeler.yaml, and this file).
Version is a patch bump from the highest existing vMAJOR.MINOR.PATCH tag,
1.0.0 when none exist. workflow_dispatch takes an explicit version for
minor and major bumps.
Runs are serialised with cancel-in-progress false: two merges landing
together would otherwise read the same highest tag, compute the same next
version, and the second would hit the reusable's existing-tag guard and
no-op, leaving that change unreleased.
release.yaml is workflow_call-only. It normalises and validates a `version`
input against MAJOR.MINOR.PATCH, skips every mutating step when the tag or a
Release for it already exists, creates an annotated tag with `git tag -a` and
publishes a GitHub Release with `gh release create --verify-tag`. Top-level
permissions grant `contents: write` only; no id-token is requested. The
previous-tag lookup and `--generate-notes` both work in a repo with no tags.
ci-mobile-ios.yaml is workflow_call-only and pairs with cd-mobile-ios.yaml,
reusing its node-version, ruby-version, working-directory,
cache-dependency-path and fastlane-lane input names. Job `js` runs npm ci,
typecheck, optional lint and optional tests on ubuntu-latest. Job `ios-build`
runs pod install and `xcodebuild build` on macos-26 with
CODE_SIGNING_ALLOWED=NO, CODE_SIGNING_REQUIRED=NO and CODE_SIGN_IDENTITY="";
it declares no secrets and performs no upload. Job `ci` aggregates both via
`needs` so a caller job keyed `ci` reports `ci / ci`. All three jobs carry
job-level concurrency with cancel-in-progress: true. Top-level permissions are
`contents: read`.
The Fastlane branch carries a per-line `# shellcheck disable=SC2086` because
fastlane requires the platform and lane as two argv entries.
The self-CI gate ran `./actionlint -shellcheck=`, and the empty value
silently disabled the shell-linting half of the check — so every `run:`
body in the reusable workflows this repo publishes was unlinted, on the
exact path that deploys to AWS.
Measured against the pinned actionlint 1.7.12 and the shellcheck the
ubuntu-latest runner ships (0.9.0-1), the real backlog was 5 findings,
not the 4 the old comment claimed. Three were genuine and are fixed in
the shell:
- cd-cdk.yaml "Publish .NET project" (SC2046): the project path was
interpolated inline and `$(dirname ...)` was unquoted, so a path
containing whitespace split into several arguments. Now passed via
env indirection and quoted, which also removes the last inline
expression interpolation from that step.
- cd-cdk.yaml / ci-python-sam.yaml "Install Python dependencies"
(SC2044 x2): `for req in $(find ...)` word-split and globbed every
path found. Replaced with a NUL-delimited `while read` loop.
Two are deliberate and are suppressed per-line, with the reasoning in a
comment directly above:
- cd-sam.yaml `sam deploy ... $PARAMS` and cd-cdk.yaml
`cdk deploy $STACKS` (SC2086 x2) rely on word-splitting so multiple
parameter overrides / stack selectors reach the CLI as separate argv
entries. Quoting them would collapse each into a single argument and
break every parameterised or multi-stack deploy, so they keep the
unquoted expansion and carry a scoped `# shellcheck disable=SC2086`.
The gate now runs plain `./actionlint` (shellcheck defaults to the
binary on PATH) and prints `shellcheck --version` first, so the check
fails loudly if a future runner image drops it instead of quietly
linting less.
cd-dotnet-eb already serialises deploys per environment; the other three
deploy reusables had no concurrency group, so back-to-back merges could
start overlapping runs against the same target. CloudFormation rejects a
concurrent update on the same stack and cd-sam/cd-cdk pre-flight already
hard-fails on an in-progress stack, so the symptom is a failed run that
needs a manual re-run rather than a corrupted deploy. Grouping makes
those deploys queue instead.
Each group key names the thing being deployed, so independent targets in
one caller repo still deploy in parallel:
cd-sam region + stack-name (both always non-empty)
cd-cdk region + stacks + stack-name
cd-mobile-ios working-directory + fastlane-lane (both default)
cd-cdk keys on the stack selector rather than stack-name because
seahaven-org-baseline calls it from five jobs in a single run, one per
AWS account, and two of those pass no stack-name. Keying on stack-name
alone would collapse them into one group and serialise five independent
per-account deploys.
cancel-in-progress is false on all three, matching cd-dotnet-eb: unlike
CI, cancelling a deploy midway can leave infrastructure mid-update.
GitHub Actions expressions are substituted into a run body as text
before bash parses it, so a value carrying a quote, a command
substitution, or a newline becomes shell syntax rather than data.
cd-sam.yaml interpolated the parameter-overrides secret straight into
a shell test and an assignment, putting secret material into the script
body. cd-cdk.yaml interpolated the caller-supplied post-deploy-script
input into a bash invocation, which is caller-controlled command
injection rather than secret exposure.
Both now use env-var indirection, matching the STACKS precedent in the
CDK deploy step. PARAM_OVERRIDES is deliberately left unquoted at the
point of use: parameter-overrides carries multiple Key=Value pairs that
must reach sam deploy as separate argv entries, so quoting it would
collapse every override into one argument and break deploys that use
it. POST_DEPLOY_SCRIPT is a single path and is quoted.
Behaviour is otherwise unchanged. An empty parameter-overrides still
produces no --parameter-overrides flag at all, and an empty
post-deploy-script is still skipped by the step-level if condition,
which is a workflow expression and not shell.
Three pieces of tooling for retired flows were still carried in this
repo, and the README documented them as if they were current.
- `.github/workflows/compliance-audit.yaml`: deprecated 2026-06-10.
Its schedule was already stripped, and its live state in the Actions
API is `disabled_manually`, so it was workflow_dispatch-only and
inert. It was also the last consumer of the `CLAUDE_CI_APP_ID` and
`CLAUDE_CI_APP_PRIVATE_KEY` org secrets.
- `scripts/rollout-review-workflow.sh`: a one-shot script that pushed a
per-repo wrapper calling `claude-code-review.yaml`. That reusable
workflow was deleted on 2026-05-13 and no longer exists in this repo
or any of the 31 org repos, so the script could only ever open PRs
for a workflow that resolves to nothing.
- README `PR Reviews`, `Scripts`, and the `claude-code-ci` GitHub App
setup steps, which described the same retired flows.
The `ANTHROPIC_API_KEY` org secret is deliberately kept in the setup
table: it is still read by the active `reviewer-eval.yml` workflow in
`open-swe`. The `CLAUDE_CI_APP_*` secrets are now unreferenced across
the org, but this change only removes their documentation. Neither the
secrets nor the App itself are touched.
Setup steps are renumbered 1-3 with no gap, and the `see §5` reference
in the IAM section is repointed to §3. actionlint 1.7.12 (the version
pinned in ci.yaml) passes clean over the remaining workflows.
ci-python-sam, ci-typescript-cdk and ci-dotnet declared no permissions
at any level, unlike every other workflow here. A reusable workflow that
declares nothing inherits the CALLER's token scopes, and these are
called from deploy repos, so a lint/test/synth job could run holding an
OIDC-mintable token it has no use for. None of the three references
GITHUB_TOKEN, github.token, gh, or any secret, so contents:read is all
they need to check out and build.
Also pass node-version explicitly in the cdk-deploy and ci-node
templates. cicd.md requires callers to pin it so lockfileVersion 3 from
local Node 24 / npm 11 cannot drift from the runner, but no template
did. Only these two targets accept the input; sam-deploy, dotnet-eb,
dependency-review and labeler do not, so they are left alone.
Verified against all 22 callers across the org that none grants
permissions omitting contents:read, so no repo's CI breaks on the
caller-cannot-be-exceeded rule.
Publishes a .NET project, packages the output as a bundle, uploads it,
creates an Elastic Beanstalk application version, and updates an
existing environment using OIDC credentials. It deploys to an
environment; it never creates one.
Two deliberate departures from the existing cd-* reusables:
- A concurrency group keyed on application+environment, with
cancel-in-progress false, so two pushes cannot deploy over each other
and an in-flight deploy is never aborted midway. The existing cd-*
workflows have no concurrency group at all.
- No input or secret is interpolated into a run: body; everything goes
through env-var indirection. The repo's actionlint runs with
shellcheck disabled, so this is a hand-maintained property.
The post-deploy check polls rather than using the CLI waiter
"elasticbeanstalk wait environment-updated": that waiter is hardcoded to
20 attempts x 20s and the CLI cannot extend it, so a slower rolling
deploy would fail the job while the deployment was still healthy. The
timeout is an input instead. The check asserts status, health and the
running version label -- Elastic Beanstalk reports a rolled-back deploy
as a healthy Ready environment running the previous version, so without
the version assertion the job would go green over a failed deploy.
Replaces the malformed cd-dotnet-eb.yaml.yml stub (doubled extension,
empty on:/jobs:). Adds the matching starter template and README entries.
Per the updated handbook convention (engineering-handbook PR #18),
reusable-workflow references use full commit SHA pins with a '# main'
comment instead of the mutable @main branch ref. Templates now ship
pinned so new repos start convention-compliant; Dependabot advances
the pin after instantiation. Commented usage examples in ci-static and
ci-typescript-frontend use the <full-commit-sha> placeholder form.
Callers with an adjudicated accepted-risk advisory (suppressed with
justification in their repo-local .security-review/suppressions.json)
had no way to keep the dependency-review check green when a lockfile
diff touches a package still inside the vulnerable range. Passes the
input straight to actions/dependency-review-action. Default '' is
byte-identical to an unset action input, so existing callers are
unaffected.
First consumer: seahaven-site, allowing GHSA-mh99-v99m-4gvg
(brace-expansion, no in-range fix until @11ty/recursive-copy bumps
minimatch).
Unpinned pip install ruff let ruff 0.16.0 pick up expanded
default lint rules, breaking every caller repo without its
own ruff config. Pinning prevents implicit rule-set changes
on new ruff releases.
Refs: #87
Co-authored-by: amoussa1229 <166072409+amoussa1229@users.noreply.github.com>
* Add stacks input to cd-cdk for multi-account apps
cdk deploy was hardcoded to --all, which breaks when one CDK app defines
stacks for two AWS accounts: whichever role the job assumed fails on the
other account's stacks. Callers can now pass per-job stack selectors;
default stays --all so existing callers are unaffected.
* Trust seahaven-org-baseline sub on account-baseline deploy role
Transition pair for the repo rename: OIDC sub claims carry the repo full
name, so the renamed repo cannot assume the role until its sub is
trusted. Old sub is removed after a post-rename deploy verifies green.
* Pass stacks selector via env var, not expression interpolation
Defense-in-depth from the security review: expression interpolation
into run: is pre-shell text substitution, so metacharacters in the
input would execute as script. Env-var expansion never re-parses shell
syntax; word-splitting for multiple selectors is preserved.
Add job-level concurrency (cancel-in-progress) to all four ci-python-app
jobs, matching the ci-python-sam idiom. Per-job group keys include
github.job so the parallel jobs in a single run do not share a group.
Replace sam-deploy starter-template stack-name: $default-branch (which
GitHub substitutes to the literal branch name main) with a
REPLACE-ME-stack-name placeholder, and point cfn-role-arn at the real
shared github-cfn-execution-role.
actions/checkout v7.0.0 (2026-06-18) is internally an ESM rebuild plus
one behavioral change: it blocks checking out a fork PR head ref under
pull_request_target / workflow_run (PR #2454). No Sea Haven workflow uses
those triggers, so there is no reachable behavior change. The Node 24
runtime requirement already landed at v6, so v6 -> v7 carries no new
runner requirement. All runners here are GitHub-hosted (ubuntu, macos).
Covers all 16 checkout pins across 12 reusable/standalone workflows plus
the dependency-review workflow-template scaffold. Consumers on @main pick
this up automatically on merge.
Adds ci-typescript-frontend.yaml, a workflow_call reusable CI for bundled
TypeScript SPAs (Vite / React / Vue with vitest + Playwright). Existing
reusable CIs do not fit this shape: ci-static is for plain HTML sites and
ci-typescript-cdk targets CDK infra repos.
The workflow runs as a single `ci` job so callers emit the `ci / ci` status
context the org branch-protection rulesets require. Steps: a Sea Haven
standards gate (required npm scripts present, plus a changed-line guard for
AI-tool footers, hook bypasses, and hardcoded secrets), then format:check,
lint, build, unit tests, and an optional Playwright browser smoke. Every step
past the standards gate is individually toggleable, and string inputs are
passed through env to avoid expression injection.
Documents the workflow in the README reusable-workflows list.
The central label rules assumed a root-level project layout
(lib/**, bin/**, cdk/**, src/**), so monorepos that nest components
under top-level dirs (infra/, web/, mobile/, shared/) matched nothing
for those areas. PRs touching only infra/lib/** or web/** ran the
labeler green but received no label.
Label coverage:
- infra: + 'infra/**' (covers infra/lib, infra/bin, infra/cdk.json)
- app: + 'web/**', 'mobile/**', 'shared/**'
Additions are appended to the existing root paths, so single-project
repos are unaffected; deliberately avoided blanket '**/lib/**' globs
that would mislabel web/src/lib/** as infra.
Hardening rolled in while here:
- Pin actions/labeler to a commit SHA (was the floating @v6 tag)
- Add a per-PR concurrency group with a run_id fallback for non-PR
callers, so rapid pushes cancel superseded label runs
- Broaden 'ci' (.github/actions/**), 'dependencies'
(Directory.Packages.props, yarn.lock, pnpm-lock.yaml, Podfile/.lock)
and 'tests' (JS/TS .test/.spec, pytest test_*.py/conftest,
.NET *Tests.cs, Java *Test.java, Go, Ruby) globs
Caller repos must already have any label a rule can emit; actions/labeler
does not create missing labels. The org 'infra' label was backfilled
across repos separately.
Reusable CI for plain Python apps / locally-run tooling that don't deploy via
SAM or CDK. Beyond ruff lint/format + the conventions audit, it adds a
collect-only import check for a root suite whose live run needs secrets, and an
isolated full pytest run for a self-contained subproject dir (whose tests/
package would collide with the root tests/ under one rootdir).
Emits the org-required `ci / ci` via an aggregator job keyed `ci` that gates on
every other job. actionlint-clean.