Concise procedures for the failure modes this system actually has. Every
workflow referenced here lives in .github/workflows/.
Detection: Deploy to AWS EKS (workflow_run, triggered by CI success on
staging) fails. The app deploys in stages (blue/green):
- The new image is rolled out on the idle slot (the deployment the service
selector is not pointing at) and must pass a pre-live
/healthsmoke — a failure here has zero user impact. - The service selector is flipped to the staged slot, then a second smoke runs through the service. On failure the selector flips back automatically (seconds) and the agent deployments roll back.
What to do after an automatic rollback:
- Confirm the rollback in the failing run: "Service selector flipped" /
"Rollback complete." log lines, and that the service selector points at the
previous slot:
kubectl get svc pixelated-empathy -n pixelated-empathy -o jsonpath='{.spec.selector.slot}' - Identify the change: the deploy ships the image digest built from the CI head that triggered it. Open the linked CI run to find the commit.
- If the flip-back itself failed, manually restore the selector to the previous
slot with
kubectl patch svc pixelated-empathy -n pixelated-empathy --type merge -p '{"spec":{"selector":{"app":"pixelated-empathy","slot":"<prev>"}}}'and verify with the same jsonpath above, then roll back the agent deployments (kubectl rollout undo deployment/<agent> -n pixelated-empathy) if the run died between their rollout and the app flip. - Re-run the deploy for staging only after the offending commit is fixed or
reverted. Deploys are dispatchable manually (
workflow_dispatch) but normally follow a green CI run.
Note: percentage-based canary (traffic split between slots) is NOT enabled — Traefik's weighted routing needs the CRD provider, which is not turned on. Rollout is staged, not fraction-split.
Detection: Monitoring (every 6h) routes CRITICAL/HIGH to Slack via
scripts/ci/health-alerts.mjs; CRITICAL pages @here and fails the run. The
routing table and per-surface ownership live in that script.
- Open the alert's link to the failed Monitoring run to see which probe failed (the step log names the surface and severity).
- For an app-level failure, check the EKS deployment health (runbook 1, step 3)
and the Sentry project — error events carry build and release tags from
Build & Sentry Release. - For an infrastructure failure (probe could not reach the service at all),
check the Traefik ingress and NLB hostname:
kubectl get svc -n traefik traefik, and confirm DNS still points at it. - Re-run the Monitoring workflow (
workflow_dispatch) after remediation.
- Read the failed step first — most Quality failures are baseline
regressions (complexity, duplication, file-size, tech-debt, naming,
boundaries, version-drift) and the log names the exact offending package or
file. Growth requires either a fix or a sanctioned re-pin:
pnpm lint:<audit> -- --update. [ERR_PNPM_OUTDATED_LOCKFILE](frozen-lockfile mismatch) means the manifest andpnpm-lock.yamldiverged — typically a merged dependabot bump without its lockfile. Fix:pnpm install --lockfile-only, verify withpnpm install --frozen-lockfile --dry-run, commit the lockfile.- Version-drift failures list the new duplicate versions. If the growth is an intentional consequence of a merged dependency bump, re-pin the baseline (see step 1); otherwise consolidate via pnpm overrides.
Error Insightposts a digest for red runs; use it for triage, not as a substitute for reading the log.
- Check the
Vercel (Frontend Test Center)andBuild & Sentry Releaseruns for the same commit — the deploy pipeline only ships after green CI, gated by the policies in.github/workflows/vercel-policy.md. - If the build is green but the site misbehaves, roll back in the Vercel dashboard to the previous deployment for the affected environment.
- Sentry releases tag each deploy; use the release view to confirm which version is live.
Detection: Accuracy gate (nightly ~03:50) fails. This workflow carries the
bias- and crisis-detection ML accuracy suites — a ~15-minute benchmark
deliberately excluded from the per-push advisory gate (same precedent as the
load/performance excludes), so a red run means an accuracy regression shipped in
the last 24 hours.
- Download the run's
accuracy-test-logartifact for the failed assertions and threshold deltas. - Identify the window: the commits merged since the previous green accuracy
run. Accuracy regressions are usually a dataset/model-threshold change, not a
code bug — check recent commits touching
tests/bias-detection,tests/crisis-detection, and the crisis/bias engines underapps/web/src/lib/ai/. - Fix forward or revert; re-run the workflow (
workflow_dispatch) to confirm green before closing out.
- Note the test name and run link. Do not add retries — retries mask non-determinism, which the anti-suppression policy bans.
- Reproduce locally, repeatedly, against the isolated pe test database (see CONTRIBUTING.md "pe test database" for the exact invocation).
- If it reproduces: fix the root cause (usually shared state, time, or ordering dependence) before re-running the suite.
- If it cannot be reproduced locally, re-run the exact workflow with
gh run rerun --failed <run-id>and record the outcome on the linked PR or issue.
The quality workflow's "ai submodule strict ratchet" job pins the strict-mypy
debt of the ai submodule's research/ tree
(scripts/ci/ai-strict-baseline.json, enforced by
scripts/ci/ai-strict-ratchet.mjs).
- Read the job log. Two failure classes:
NEW <file> :: <code>on an existing file, orGREWon an existing key — the debt increased. Fix the new errors (real typing repairs, no per-line ignores); never re-pin upward.New files with strict errors— a newly added file arrived non-strict-clean. Fix it before it can be exempted; the exemption never expands to new code.
- If errors were removed, bank the cleanup deliberately:
node scripts/ci/ai-strict-ratchet.mjs --update, review the diff (counts must only shrink), and commit the baseline. - After deleting legacy files, run
--pruneto drop them from the baseline. - mypy runs dependency-light (
uv run --no-project --with mypy,--ignore-missing-imports), so third-party imports resolve toAny. Reproduce locally with the exact same script — do not compare its numbers against a full-venv mypy run.
Multiple workflows fail at "Init submodules" with
pinned commit <sha> is NOT fetchable from github.com/daggerstuff/…: the parent
gitlink points at a submodule commit that exists locally but was never pushed to
the GitHub mirror (usually a parallel session pushed the submodule to bitbucket
only).
- Confirm the local submodule has the pinned commit:
git submodule statusandgit -C ai rev-parse HEAD(ordocs). - Fast-forward the mirror branch — never force-push:
git -C ai push origin HEAD:staging(docs usesmaster). This is a mirror sync, not a history rewrite. - Rerun the failed workflows:
gh run rerun --failed <run-id>. - Root habit to fix: every session that bumps a submodule gitlink must push the submodule commit to BOTH remotes before pushing the parent. The CI init-submodules.sh check cannot rescue a ref that exists nowhere.
Never resolve lockfile conflict markers by hand. The lockfile is a pure function of the merged manifests, so the correct result is whatever pnpm regenerates — hand-merging produces states CI rejects and eats the most time of any conflict class.
-
While the merge is conflicted, run one command:
make lockfile-resolve(or
scripts/devops/resolve-lockfile-conflict.sh --add). It seeds the lockfile from the incoming side, regenerates it from the merged package.json + pnpm-workspace.yaml viapnpm install --lockfile-only, and stages the result. Then commit the merge as usual. -
Prevention: merge
staginginto long-lived PR branches early and often — most of these conflicts are just two branches churning the same serialized dependency graph, and frequent merges keep the churn small. -
If a lockfile is merely stale (not conflicted), do not hand-edit it either:
pnpm install --lockfile-onlyre-syncs it.