Troubleshooting Guide
On this page
Devstack Issues
Port Conflicts on Start
Symptom: cargo xtask dev start fails with "port already in use"
Fix:
docker compose --profile full down --remove-orphans docker ps -a # check for orphaned containers docker rm -f <container-id> # remove if found cargo xtask dev start --shared-db
Container Name Collision
Symptom: "The container name /canopy-canopy-tanf-1 is already in use"
Cause: Docker has orphaned containers from a prior run that weren’t fully removed.
Fix:
docker compose --profile full down --remove-orphans
The devstack staleness guard now passes --remove-orphans to all compose calls automatically.
Stale Code in Containers
Symptom: Tests pass locally but fail against devstack, or devstack behavior doesn’t match recent code changes.
Cause: Containers running old binaries. Docker cached the previous build.
Fix:
cargo xtask dev reload --shared-db
Or use the staleness guard: cargo xtask dev status shows whether devstack is current. cargo xtask dev refresh performs the minimum rebuild needed.
Garage S3 Crash on Start
Symptom: Garage container exits immediately with configuration error.
Cause: Missing rpc_bind_addr field in devstack/garage/garage.toml (required by Garage v2.2.0+).
Fix: Ensure garage.toml contains:
rpc_bind_addr = "[::]:3901" rpc_secret = "..."
Shared-DB Mode Issues
Symptom: Program service fails to start in --shared-db mode.
Cause: The shared PostgreSQL instance may not have all program databases created.
Fix: Databases are created by each service’s sqlx::migrate!() call, but the database itself must exist first. Check that all canopy_* databases are created on the shared instance.
SOPS Decrypt Fails on Cold Start/Restart
Symptom: cargo xtask dev start or cargo xtask dev restart aborts with Error: parse sops JSON: expected value at line 1 column 1.
Cause: Only a cold dev start or dev restart re-decrypts secrets via SOPS (dev refresh and dev clean reuse the running stack and do not re-decrypt). The error means SOPS isn’t resolving the age key — it ran but emitted nothing. The decrypt path is sops --decrypt --output-type json secrets/dev.yaml, which needs the age key at ~/.config/sops/age/keys.txt.
Fix: 1. Confirm SOPS can decrypt directly — it should emit JSON:
sops --decrypt --output-type json secrets/dev.yaml
-
If that fails, check that the age key exists at
~/.config/sops/age/keys.txtand thatsopsis onPATH. -
For config changes that do not need a Keycloak realm re-import, prefer
cargo xtask dev refresh— it does not re-decrypt secrets and avoids the fragile cold path entirely. Reservedev restartfor changes that truly require re-importing the realm.
Keycloak Issues
Password Grant Fails (No Token)
Symptom: acquire_token_for("jane.doe", "password") returns None.
Causes:
1. Keycloak not running: check docker compose ps keycloak
2. emailVerified not set to true in devstack/keycloak/definitions.json
3. Keycloak realm not imported: check Keycloak admin console at http://localhost:8180
JWT Validation Fails (Unknown kid)
Symptom: All authenticated requests return 401 even with a valid token.
Cause: JWKS cache is stale or Keycloak rotated keys.
Fix: The JwksProvider auto-refreshes on unknown kid with a 30-second debounce. Wait 30 seconds and retry. If persistent, restart the affected service to force a fresh JWKS fetch.
Split Issuer/Fetch URLs
Symptom: JWT iss claim doesn’t match KEYCLOAK_ISSUER setting.
Cause: In Docker, the public issuer URL (how browsers see Keycloak) differs from the internal Docker URL (how services fetch JWKS).
Fix: Set both:
CANOPY_{SVC}__KEYCLOAK_ISSUER=http://host.docker.internal:8180/realms/canopy # public
CANOPY_{SVC}__KEYCLOAK_URL=http://keycloak:8080/realms/canopy # internal
Test Issues
Integration Tests Skip Silently
Symptom: Integration tests show 0 passed, 0 failed (all skipped).
Cause: Devstack not running. infrastructure_available() returns false and tests skip.
Fix: Start devstack first: cargo xtask dev start --shared-db
In CI, set CANOPY_CI=true — the guard panics instead of skipping, ensuring tests never silently skip.
nextest Takes Minutes to Compile
Symptom: cargo nextest run takes 1-2 minutes even when code hasn’t changed.
Cause: clippy ran with dev profile, nextest uses test profile — separate compilation targets.
Fix: cargo xtask validate runs clippy with --profile test to share artifacts. If running manually, use:
cargo clippy --all-targets --profile test -- -D warnings cargo nextest run --workspace --profile integration
Transient Test Failures
Symptom: canopy-rules::rules_test evaluation_creates_audit_trail fails intermittently.
Cause: Timing-dependent test — publishes an event and checks if canopy-security persisted it. RabbitMQ delivery can be delayed.
Fix: Rerun. If persistent, check RabbitMQ health: docker compose logs rabbitmq.
Pre-push Reseeds and Mutates the Running Devstack
Symptom: After a git push, a custom seed is gone — the database holds the default 50-household seed and the E2E-run mutations instead.
Cause: The .githooks/pre-push hook runs a bare cargo xtask e2e (which re-seeds internally via xtask::cmd::seed::run) against the same long-lived canopy devstack — not ephemeral testcontainers. Every push therefore re-seeds and mutates the DB. This is unavoidable: the hook always does it, and --no-verify is forbidden.
Implication: Do any manual setup after pushing, or expect to re-seed. The safe order is merge first, re-seed last:
# after the push lands and the MR merges: cargo xtask dev clean --confirm && cargo xtask dev start cargo xtask seed # since #1142 the loader always resets the service DBs first
Mitigations (cheapest first): 1. Snapshot/restore around the E2E run using the existing tooling, preserving dev/demo state with near-zero perf hit (see ADR-016):
cargo xtask migrate snapshot # before cargo xtask migrate rollback # after, to restore
-
Run a separate compose project for true namespace isolation on the same daemon (shares the image cache, ~2x resources while running):
COMPOSE_PROJECT_NAME=canopy-e2e cargo xtask dev start
Note: Docker-in-Docker (DinD) is not recommended locally. State isolation, not daemon isolation, is what’s wanted — and DinD loses the Rust/musl build cache (cold ~10-minute builds every run) and doubles resource pressure across the ~29 services. "Fast DinD" via a mounted docker.sock is just sibling containers on the host daemon, which gives no isolation over a separate compose project.
Cargo Issues
cargo audit Reports Vulnerabilities
Symptom: cargo audit reports RUSTSEC advisories.
Cause: Transitive dependencies via typst (document generation). 4 advisories are suppressed in deny.toml:
- RUSTSEC-2024-0320 (yaml-rust unmaintained)
- RUSTSEC-2025-0141 (bincode unmaintained)
- RUSTSEC-2024-0436 (paste unmaintained)
- RUSTSEC-2023-0071 (rsa Marvin Attack)
Fix: These are all transitive via typst and have no upstream fix. cargo deny check is the authoritative tool (subsumes cargo audit). The advisories are documented and monitored — when typst releases a fix, remove the ignore entries from deny.toml.
Git / Push Issues
git push Dies with SIGPIPE (exit 141) After a Green Pre-push
Symptom: git push exits 141 immediately after the pre-push hook reports success — the validation passed but the branch never lands on the remote.
Cause: The large pre-push E2E output (341 specs) floods the output capture and SIGPIPEs the push after the hook passed but before the transfer completes. The hook’s work is done; only the final transfer is killed.
Fix: Redirect the push output to a file so the capture only receives a tiny line, then re-push if the branch isn’t on the remote yet:
git push --set-upstream origin <branch> > /tmp/push.log 2>&1; echo PUSH_EXIT=$?
Validation already passed, so a plain re-push lands the branch.
Database Issues
sqlx Compile-Time Verification Fails
Symptom: Build fails with "error returned from database: relation does not exist"
Cause: sqlx verifies SQL queries at compile time against DATABASE_URL. If the database is down or the migration hasn’t run, verification fails.
Fix:
1. Ensure PostgreSQL is running and the database exists
2. Run the service once to apply migrations: cargo xtask dev start --shared-db
3. Alternatively, use offline mode: SQLX_OFFLINE=true cargo build
Migration Fails on Startup
Symptom: Service panics with "migrations failed"
Cause: Migration SQL has a syntax error, or a migration was modified after it was already applied.
Fix:
- Check the migration file for SQL errors
- If a migration was modified: migrations are forward-only. Write a corrective migration, or restore from backup (see Deployment Rollback)
- If the database is corrupted: cargo xtask dev restart --shared-db wipes all data and starts fresh (development only)