ingestion: retry OIDC discovery instead of exiting on cold-boot 502 #83
Closed
opened 2026-07-17 12:15:08 +00:00 by mathias
·
4 comments
No Branch/Tag Specified
main
fix/pipeline-writes-158
fix/edit-path-io-192
fix/create-regular-188
fix/guard-special-179
fix/linkcheck-keeps-write-178
fix/write-dir-redirect-173
fix/tunnel-detect-174
fix/pipeline-root-157
w3/154-mcp-auth-wiring
fix/tunnel-traversal
test/in-brain-targets-171
fix/brain-scan-root-157
fix/graph-wiki-root-157
fix/vectorstore-root-157
fix/notes-root-157
fix/search-root-157
fix/brainwide-root-157
fix/raw-symlinks-153
feat/symlink-tripwire-157
fix/rest-auth-149
fix/123-brain-edit-envelope-check
feat/106-page-aware-extraction
fix/105-source-drift-on-main
fix/104-surface-merge-conflicts-on-main
agent/b1-compile-on-ingest
agent/104-surface-merge-conflicts
agent/105-source-drift
agent/107-loud-unconfigured-alerting
feat/brain-read-edit
fix/capture-i1-refusal-names-target
renovate/configure
feat/consolidate-single-harness
icebox/cmd-routing
feat/brain-pending-promote
fix/close-session-classification-refresh
fix/capture-summarywriter
v0.24.0
v0.23.1
v0.23.0
v0.22.0
v0.21.1
v0.21.0
v0.20.1
v0.20.0
v0.19.1
v0.19.0
v0.18.4
v0.18.3
v0.18.2
v0.18.1
v0.18.0
v0.17.0
v0.16.1
v0.16.0
v0.15.0
v0.14.0
v0.13.2
v0.13.1
v0.13.0
v0.12.1
v0.12.0
v0.11.2
icebox/cmd-routing-2026-07-01
v0.11.1
v0.11.0
v0.10.0
v0.9.0
v0.8.0
v0.7.0
v0.6.0
v0.5.0
v0.4.1
v0.4.0
v0.3.0
v0.2.0
v0.1.0
Labels
Clear labels
agent/ready
blocked/client
blocked/supplier
blocked/upstream
risk/high
risk/low
risk/medium
size/l
size/m
size/s
size/xs
status:dispatch
status:dispatched
status:review
status:reviewing
type/chore
type/decision
type/docs
type/feat
type/fix
type/ops
type/research
type/test
Meets the agent-ready contract; authorizes unattended execution
Waiting on the client
Waiting on a supplier
Waiting on an upstream project or vendor
Irreversible, security-relevant, or control plane
Reversible, local, test-covered
Touches shared state, deploys or data
Too big - split it
Under two days
Under half a day
Under an hour
Maintenance, no behaviour change
Human decision required; never agent/ready
Documentation only
New capability
Corrects broken behaviour
Infrastructure, deploy, runtime
Investigate; output is a finding, not code
Test coverage only
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: mathias/hyperguild#83
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
supervisor/ingestionexits on startup if OIDC discovery fails, instead of retrying. On a cold cluster boot it races ahead of dex and dies.Observed 2026-07-17 after a koala reboot — CrashLoopBackOff, 4 restarts before it caught:
Note the ordering: it completes all its real work — postgres hybrid retrieval connected, graph backfill of 794 nodes, three session batches ingested — and only then dies on the JWT validator. Every restart redoes that work.
The
502is ingress-nginx failing to reach dex, which wasn't serving yet. Nothing was misconfigured; ingestion just started first.Why it matters
It self-heals via CrashLoopBackOff (it recovered once dex came up), so this is low severity in effect. But:
Prior art
The brain already has this pattern:
knowledge/flux-kustomization-depends-on-bootstrap-ordering.md— "prerequisite resources must be wrapped in their own Kustomization withdependsOnentries on any Kustomization that requires them". This is another instance of the same cold-bootstrap race, at app level rather than Flux level.Proposed fix
Retry OIDC discovery with bounded exponential backoff at startup rather than exiting on first failure. A transient 502 from a not-yet-ready auth upstream is expected during cold boot, not fatal.
Options:
dependsOn— order the ingestion Kustomization after dex/ingress. Consistent with the recorded prior art, but doesn't help outside Flux and doesn't cover a dex restart at runtime.(1) or (3); they compose.
Secondary
Consider constructing the JWT validator before the expensive graph backfill and ingestion work, so a fatal config error fails fast instead of after several seconds of redone work each cycle.
Acceptance criterion
A cold cluster boot brings ingestion to
1/1 Runningwith 0 restarts. Regression test: OIDC discovery returning 502 on first N attempts then succeeding should not crash the process.Source
ingestion/internal/claudewatcher+cmd/server/main.goin this repo. (mathias/ingestion-svcis a tombstone — "superseded by hyperguild/ingestion", 2026-05-14.)Fixed with (1) app-level retry + (3) fail-fast ordering —
9a232ad. They compose, as the issue predicted, and each fixes a different half.Retry: 8 attempts, 1s doubling to a 30s cap, wrapping
chassisauth.NewJWTValidator. A 502 from a not-yet-serving dex is now aWARNline per attempt instead ofos.Exit(1).Ordering (the secondary ask): the validator is built immediately after
ctx, ahead of the watcher, the graph backfill and claudewatcher. So a real config error — wrongDEX_ISSUER_URL, wrong audience — now fails in seconds instead of after 794 nodes and three session batches.Why the budget stays bounded
Retrying forever is the tempting version and it is worse. A wrong issuer URL would become a process that is
Running, answers no authenticated request, and never says why — strictly harder to diagnose than the crash loop this issue is about. The bound keeps the loud failure for the failure that deserves it, and removes it for the benign race.startupretry.Dois generic and tested directlymain()wiring is not testable, so the logic is a package. Five properties pinned, red first:errors.Isstill finds the original diagnosiscontext.Canceled, not after the full backoff(nil, nil)That last one matters here specifically because
NewJWTValidatorreturns(nil, nil)whenDEX_ISSUER_URLis empty. A retry helper that quietly did nothing would be indistinguishable from auth-disabled.Not verified
The acceptance criterion — cold cluster boot reaching
1/1 Runningwith 0 restarts — needs a koala reboot, which is not something to do unattended.task checkis green and the unit test reproduces the exact 502-then-success sequence; the real confirmation is the next cold boot.Also unverified, and still worth checking when someone is at a terminal: whether the redone graph backfill and batch ingestion were genuinely idempotent. The offsets suggested yes, and after this change a restart is far less likely to happen at all — but "less likely to happen" is not the same as "was safe when it did", and this issue is the only place that question is written down.
Leaving open until a cold boot confirms it, since that is the stated acceptance.
🤖 dispatch: routing: executor=claude (default — no executor: label) — starting headless run
🤖 dispatch: executor claude failed: runner error: agent run ended in error (see stderr)
Closed by
432e09d. The retry itself shipped in9a232ad; this commit adds the stated regression test against a real issuer. I extractedmain()'s closure intobuildJWTValidatorwithout changing behaviour, and tested it with anhttptestOIDC issuer:status 502)context.CanceledpromptlyFlip test: setting
Attempts=1turns all three red.CI=1 task checkexits 0.Still not verified: the live acceptance, a cold koala boot reaching 1/1 Running with 0 restarts. That needs a reboot, which I won't do unattended. I also removed the stale
status:dispatchedlabel.Created via git-mcp on behalf of @mathias