RCA — Two independent, non-blocking CI findings surfaced during release-2026.08.19 staging shepherd
Incident ID: 2026-08-19-ci-boundary-velvet-deploy-false-positive-refspec
Date: 2026-08-19
Severity: SEV-3 (non-blocking; discovered proactively, not user-reported)
Duration: N/A — no outage; both findings were latent/undetected until this shepherd run exercised the paths
Blast radius: CI infrastructure only. deploy-staging (the tag-scoped pipeline gating actual staging deploys) was unaffected and completed 8/8 green. No customer-facing impact.
Author: sre-agent
Summary
While shepherding the tag-scoped deploy-staging pipeline for release-2026.08.19
(develop HEAD 3506b3e02), the companion push-triggered pipeline (ci-boundary,
ci-console, deploy-velvet, deploy-queue, deploy-antlers-next-staging,
infra-ci-validate, terraform-validate — all fired by the same gatekeeper push
to release) surfaced two independent, non-blocking findings: (1) a gitleaks
false positive on a brand-new Wave 3 idempotency test file that follows an
already-allowlisted convention but was never added to the allowlist, compounded
by a separately-broken report-upload step that has silently failed to publish
itemized findings for an unknown period; and (2) a deploy-velvet.yaml bug —
introduced 2026-07-08, latent for ~6 weeks — that silently corrupts the Heroku
push refspec to empty, which Heroku's pre-receive hook correctly rejects. Both
are CI-only; deploy-staging and the actual staging deploy were unaffected.
Timeline (all times UTC)
- 19:18 —
deploy-staging(Woodpecker pipeline 6540, tagrelease-2026.08.19) completes 8/8 steps green; staging release v1062 live,RELEASE_VERSION=0.0.112-6540. - 19:18 — Gatekeeper's
git merge --no-ffpush torelease(commit923dd9f4) fans out to a second, push-triggered Woodpecker pipeline (6541) coveringci-boundary,ci-console,deploy-antlers-next-staging,deploy-queue,deploy-velvet,infra-ci-validate,terraform-validate. - ~19:25 —
deploy-antlers-next-staging,infra-ci-validate,terraform-validatecomplete success.ci-boundaryfails at thegitleaksstep (leaks found: 1);gitleaks-report-uploadalso fails (vault 404). - ~19:33 —
deploy-velvetfails at itsdeploystep: Heroku rejects the push ("Push rejected, cannot delete main branch") 5x. - 19:33–19:40 — sre-agent reproduces the gitleaks finding locally (matching WP's
exact 2330-commit scan via
--log-opts=origin/releaseand the release branch's own.gitleaks.toml), confirms it is a test-fixture false positive ("SECRET_KEY": "test-sc-ik-5-never-prod"inbackend_v2/tests/test_idempotency_billing_wave3_4149.py, same shape as three prior allowlisted entries), and separately diagnoses thedeploy-velvet.yamlrefspec bug from the step log +git blame. - 19:36 — Wave 3 route probe on staging confirms the five idempotency-gated
billing routes are live (
401auth-required, not404), independently confirmingdeploy-staging's success and giving a code-signature anchor for the release. - 19:45 — Two
type:reliabilityissues filed (#4513deploy-velvetfix, #4514 gitleaks allowlist + vault-path gap); this RCA and aci-woodpecker.mdrunbook update written.ci-console(test-only failure,nodebinary missing in the WP test image — out of sre-agent scope, flagged for routing) anddeploy-queuestatus noted as unresolved at report time.
Impact
- Users affected: none.
- User-visible symptoms: none.
- Data integrity: ok.
- Revenue / billing: ok — Wave 3 idempotency routes confirmed live and auth-gated as expected; no billing-path impact from either finding.
- CI-visible impact:
ci-boundaryanddeploy-velvetshow red on everyrelease/mainpush until fixed, training the team to treat that as noise.deploy-velvethas had zero successful deploys since 2026-07-08 (six weeks), though novelvet/**code change has actually needed deploying in that window — so no service is currently running stale code as a result of this bug, but the next real Velvet change will silently fail to ship (loud CI red, but the running dyno stays on old code) until #4513 is fixed.
What went well
- The tag-scoped
deploy-stagingpipeline itself is well-isolated: none of these companion-pipeline failures touched it, and the shepherd bar (8 steps, release-phase migration, alembic heads, code-signature probe, RELEASE_VERSION, no-ff promote, CF Pages) all passed cleanly and independently. - Both root causes were fully diagnosable from Woodpecker step logs + local
git blame/git showreproduction without needing write access or guessing — the WP${VAR}vs$VARinterpolation gotcha was already documented (Failure mode E-4 inci-woodpecker.md) with an exact matching precedent, and the gitleaks chronic-FP allowlist pattern was already established (#4385 → #4445 → #4465) with a clear file-naming convention to follow. --redacton the gitleaks report meant local reproduction could confirm the false positive without ever exposing a real secret value in this transcript.
What didn't go well
- Failure mode E-4 (
ci-woodpecker.md) was documented with an exact "Lint check" grep command after its first occurrence (2026-07-05), but that check is manual-only — nothing in CI runs it automatically.deploy-velvet.yamlreintroduced the identical anti-pattern three days later (2026-07-08) and it went undetected for six weeks because the buggy step's path filter never matched a push until now. - The gitleaks chronic-FP allowlist (#4385/#4445/#4465) is a per-file exact-path
list, not a pattern covering the whole
test_idempotency_*.pyfamily it was established for — so each new wave repeats the same triage cycle instead of being covered automatically. - The
gitleaks-report-uploadstep's vault path (/MooseQuest/aws/ci-boundary/gitleaks-report-writer/) has apparently never been provisioned (404, not "secret rotated/expired"), meaning itemized gitleaks findings have never successfully reached S3 — every prior finding (true or false positive) had to be triaged from the truncated step-log summary alone, same as this one, unless someone reproduced it manually like this run did.
Root cause analysis
- Contributing factor 1 (deploy-velvet): The system allows a previously-fixed
anti-pattern class (WP
${VAR}interpolation stripping shell-local variables to empty) to be reintroduced in new files with no automated check — only a runbook-documented manual grep. A documented fix that isn't enforced is not a fix, it's a hope. - Contributing factor 2 (deploy-velvet latency): Path-filtered pipelines can carry a broken step silently for arbitrarily long if the path filter itself rarely matches — there is no periodic/synthetic exercise of low-traffic deploy paths, so a break is only discovered the next time a real change (or, as here, an unrelated workflow-file edit) happens to trigger it.
- Contributing factor 3 (gitleaks allowlist): The allowlist mechanism requires exact per-file additions rather than a pattern covering an already-identified recurring class, so the same triage cost repeats on every new instance of an already-understood false positive.
- Contributing factor 4 (report-upload vault gap): No provisioning
verification step confirms the
gitleaks-report-writerIAM credential and its vault path exist together before the step is relied upon — the gap was only visible by reading the step's own log output during an unrelated diagnosis.
Detection
- What alerted us: none of these were alerted — discovered by manually reviewing
the companion pipeline's step statuses while shepherding the primary
deploy-stagingpipeline, because the task's own standing bar ("fix forward anything broken") prompted a look past the immediate success signal. - How long between cause and detection:
deploy-velvet— ~6 weeks (introduced 2026-07-08, detected 2026-08-19). Gitleaks allowlist gap — hours (Wave 3 test file merged 2026-08-19, ci-boundary ran within the hour on promotion). - How to detect faster next time: see action items — CI-enforced lint for the
${VAR}class, and either a broader allowlist regex or atype:reliabilityauto-file on any newbackend_v2/tests/test_idempotency_*file lacking the allowlist entry.
Resolution
- Not yet applied — both are config-only fixes filed as
type:reliabilityissues with exact diffs for the operator/next write-capable agent to apply (sre-agent does not commit; seedocs/ops/runbooks/velvet-operator.mdanddocs/ops/runbooks/gitleaks.mdfor the relevant systems). ci-woodpecker.mdFailure mode E-4 updated with this occurrence as a documented precedent for the next diagnosis.
Action items
| # | Action | Owner | Due | Issue |
|---|---|---|---|---|
| 1 | Fix deploy-velvet.yaml ${SUBTREE_SHA} → $SUBTREE_SHA (exact diff in issue) |
operator / next write-capable agent | 2026-08-26 | #4513 |
| 2 | Add test_idempotency_billing_wave3_4149.py to .gitleaks.toml chronic-FP allowlist (or generalize the regex to the whole test_idempotency_* family) |
operator / next write-capable agent | 2026-08-22 | #4514 |
| 3 | Provision AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY for raxx-ci-boundary-gitleaks-report-writer at /MooseQuest/aws/ci-boundary/gitleaks-report-writer/ (verify Terraform-side IAM user exists first) |
operator | 2026-08-26 | #4514 |
| 4 | Convert ci-woodpecker.md's Failure mode E-4 "Lint check" from a manual grep instruction into an actual CI gate over all .woodpecker/*.yaml (flagging ${LOCAL_VAR} where LOCAL_VAR isn't a WP built-in / environment: key) so this class can't recur a sixth time |
operator / sre-agent | 2026-09-02 | (file separately if not folded into #4513) |
| 5 | Route ci-console console-tests failure (11 tests, FileNotFoundError: node — missing node binary in the WP ci-console test image) to feature-developer/CI-image owner; out of sre-agent scope (test-tooling gap, not app logic or deploy-blocking) |
operator | 2026-08-22 | (not yet filed — flagged in shepherd report) |
References
- Runbook:
docs/ops/runbooks/ci-woodpecker.md(Failure mode E-4, updated this run) - Runbook:
docs/ops/runbooks/gitleaks.md - Runbook:
docs/ops/runbooks/velvet-operator.md - Issues: #4513 (deploy-velvet), #4514 (gitleaks allowlist + vault path)
- Woodpecker pipelines: 6540 (
deploy-staging, success, 8/8), 6541 (push-to-release companion pipeline) - Prior precedent:
docs/ops/runbooks/ci-woodpecker.mdFailure mode E-4, fixed 2026-07-05 acrossvcpkg-manifest-check.yaml,migration-collision-check.yaml,maxmind-asn-refresh.yaml,alembic-version-check.yaml - Gitleaks chronic-FP precedent: #4385, #4445, #4465