Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix: remove standalone AI Gateway http listener dependency on loading providers - #27303

Merged
pawbana merged 6 commits into
mainfrom
pawel/aigov-521-decouple-ai-gateway-healthz-from-initial-provider-load
Jul 23, 2026
Merged

fix: remove standalone AI Gateway http listener dependency on loading providers#27303
pawbana merged 6 commits into
mainfrom
pawel/aigov-521-decouple-ai-gateway-healthz-from-initial-provider-load

Conversation

@pawbana

@pawbana pawbana commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Fixes an issue where the standalone AI Gateway waited for the initial provider load before starting its HTTP server.

HTTP serving now starts independently of provider synchronization. /healthz becomes available when the HTTP server starts, while /readyz requires an active DRPC connection and completed initial provider load.

Enables the Helm chart's startup and liveness probes by default because liveness no longer depends on provider loading.

@linear-code

linear-code Bot commented Jul 16, 2026

Copy link
Copy Markdown

AIGOV-521

pawbana commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

@pawbana
pawbana force-pushed the pawel/aigov-521-decouple-ai-gateway-healthz-from-initial-provider-load branch 2 times, most recently from 95a8baa to f5f8e91 Compare July 16, 2026 17:02
@pawbana
pawbana force-pushed the pawel/aigov-318-create-helm-chart-for-standalone-ai-gateway branch from 41c0f78 to e3686d1 Compare July 16, 2026 17:02
@pawbana
pawbana force-pushed the pawel/aigov-521-decouple-ai-gateway-healthz-from-initial-provider-load branch from f5f8e91 to 3fe059b Compare July 20, 2026 08:37
@pawbana
pawbana force-pushed the pawel/aigov-318-create-helm-chart-for-standalone-ai-gateway branch from e3686d1 to 644873c Compare July 20, 2026 08:37
@pawbana
pawbana force-pushed the pawel/aigov-521-decouple-ai-gateway-healthz-from-initial-provider-load branch 2 times, most recently from c95d08c to 8633b3c Compare July 20, 2026 11:29
@pawbana
pawbana force-pushed the pawel/aigov-318-create-helm-chart-for-standalone-ai-gateway branch from a274b42 to bac89f9 Compare July 20, 2026 11:29
@pawbana
pawbana force-pushed the pawel/aigov-521-decouple-ai-gateway-healthz-from-initial-provider-load branch from 8633b3c to a320f49 Compare July 20, 2026 11:51
@pawbana
pawbana force-pushed the pawel/aigov-318-create-helm-chart-for-standalone-ai-gateway branch 2 times, most recently from 8249272 to 27e7f4c Compare July 20, 2026 12:54
@pawbana
pawbana force-pushed the pawel/aigov-521-decouple-ai-gateway-healthz-from-initial-provider-load branch 2 times, most recently from 331f7c9 to 8b7d8c3 Compare July 20, 2026 13:30
@pawbana

pawbana commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-07-22 11:17 UTC by @pawbana
Spend: $110.33 / $100.00

Review history
  • R1 (2026-07-20): 18 reviewers, 6 Nit, 5 Note, 3 P3, COMMENT. Review
  • R2 (2026-07-22): 16 reviewers, 8 Nit, 7 Note, 1 P2, 4 P3, COMMENT. Review
  • R3 (2026-07-22): 13 reviewers, 9 Nit, 8 Note, 6 P3, COMMENT. Review

deep-review v0.9.0 | Round 3 | fe8af0a..c267018

Last posted: Round 3, 23 findings (6 P3, 9 Nit, 8 Note), COMMENT. Review

Finding inventory

Finding inventory - PR #27303

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P3 Author fixed (verified R2) aigatewaystart.go:419 daemon.Done() abort branch in loadProviders lost its only test; 0% coverage R1 Netero P3, Meruem P3, Bisky P3, Hisoka P3, Pariston P3, Chopper P3, Ryosuke P3, Zoro P3, Knov P3, Kite P3, Mafu-san P2, Takumi Note, Mafuuu Nit Yes
CRF-2 P3 Author fixed (verified R2) aigatewaystart.go:415 loadProviders doc comment deleted, retry/empty-list contract undocumented R1 Netero P3, Leorio P3 Yes
CRF-3 P3 Author fixed (verified R2; residual dead arm split to CRF-15) aigatewaystart.go:368 run() error-propagation arms + errors.Join aggregation uncovered R1 Bisky P3, Netero Note Yes
CRF-4 Note Author fixed (verified R2) helm/ai-gateway/values.yaml:127 Liveness on static /healthz never restarts a wedged-but-alive daemon R1 Hisoka, Mafuuu, Pariston, Ryosuke, Knov Yes
CRF-5 Note Author fixed (verified R2) aigatewaystart.go:355 provReloadErr watch-escalation send is unreachable dead code R1 Zoro, Knov, Kite, Mafuuu, Hisoka Yes
CRF-6 Note Author fixed (verified R2) aigatewaystart.go:287 providersLoaded write-once latch; readyz can report ready with stale providers after reconnect R1 Meruem Yes
CRF-7 Note Author fixed (verified R2) aigatewaystart.go:297 newStandaloneGateway fuses daemon construction and start; latent leak if caller returns before run R1 Hisoka, Ryosuke Yes
CRF-8 Note Author fixed (verified R2) aigatewaystart.go:388 "provider synchronization did not stop in time" error omits timeout and that shutdown proceeded R1 Leorio, Chopper Yes
CRF-9 Nit Author fixed (verified R2) aigatewaystart.go:154 Tracing shutdown inlined instead of existing shutdownWithTimeout helper R1 Mafuuu, Meruem, Robin, Kite Yes
CRF-10 Nit Author fixed (verified R2) aigatewaystart.go:406 Daemon shutdown reimplements shutdownWithTimeout/Close inline R1 Robin Nit, Zoro Note Yes
CRF-11 Nit Author fixed (verified R2) helm/ai-gateway/values.yaml:198 terminationGracePeriodSeconds comment understates the 3-component budget R1 Ryosuke Yes
CRF-12 Nit Author fixed (verified R2) aigatewaystart.go:392 Shutdown comment's first sentence restates http.Server.Shutdown docs R1 Gon (P2 downgraded) Yes
CRF-13 Nit Author fixed (verified R2) aigatewaystart.go:44 shutdownTimeout is the odd name among daemon/http/trace siblings R1 Gon Yes
CRF-14 Nit Author fixed (verified R2) aigatewaystart.go:303 Local server names a *standaloneGateway; collides with daemon/httpServer fields R1 Gon Yes
CRF-15 P3 Flake+message fixed R3 (verified); dead-arm residual re-raised R3 (P3) aigatewaystart.go:369 R2 initialProviderLoadErr arm: flake+nondeterministic message fixed at root R3; channel+send+serve arm now provably unreachable dead code R2 Netero P2, Komugi P2, Meruem P3, Bisky Note, Knov Note, Kite Note, Razor Note Yes
CRF-16 Nit Author fixed (c267018) aigatewaystart.go:299 Typos in new runStandaloneGateway doc comment: "DRCP"->"DRPC" (l299), "aibrideged"->"aibridged" (l302) R2 Netero, Mafu-san, Mafuuu, Gon, Leorio, Ryosuke, Chopper, Knov, Kite, Razor Yes
CRF-17 Note Author fixed (c267018) helm/ai-gateway/values.yaml:122 startupProbe enabled on /healthz gates nothing now that /healthz answers on listener bind R2 Pariston, Ryosuke, Chopper, Knov Yes
CRF-18 Note Author fixed (c267018) aigatewaystart.go:389 Single shutdownCtx shared by provider-sync join and HTTP drain; budget name overstated and invariant relaxed on timeout branch R2 Hisoka, Mafuuu, Meruem, Takumi Yes
CRF-19 Nit Author fixed (c267018) aigatewaystart.go:393 provReloadJoinErr name implies errors.Join result but holds a stop-timeout error R2 Gon Yes
CRF-20 P3 Author fixed (verified R3) aigatewaystart.go:436 readyz positive path (daemon.Ready() && providersLoaded == true) never exercised end-to-end; only false side + stub predicate tested R2 Bisky Yes
CRF-21 Nit Open aigatewaystart.go:394 Two vocabularies (provReload* vs providerSync*) name the same provider-reload goroutine in one function; CRF-19 rename landed incompletely R3 Gon (P3 downgraded) Yes
CRF-22 P3 Open aigatewaystart.go:300 runStandaloneGateway doc comment says it "establishes a DRPC connection to coderd ... and starts", implying connect-then-serve, contradicting the PR's independence invariant (aibridged.New connects async) R3 Leorio Yes
CRF-23 Note Open aigatewaystart.go:374 Successful-load -> WatchProviderReload handoff in serve's goroutine has 0% coverage; no serve/run test drives a successful initial load R3 Bisky, Mafu-san, Mafuuu, Takumi Yes

Round log

Round 3 (cross-check)

Churn guard PROCEED. Panel verified all 6 R2 fixes: CRF-16/17/18/19/20 genuinely and completely fixed; the CRF-15 flake and nondeterministic message are cured AT THE ROOT (Komugi/Takumi/Hisoka/Mafu-san/Razor stress-verified -race -count=300..500; daemon.Done() is now the sole deterministic terminal reporter). CRF-18 budget split verified: providerSync 5 + http 300 + daemon 5 + trace 5 = 315 < 330, code const comment and values.yaml agree. CRF-20 TestStandaloneGatewayHealthAndReadiness drives the real ready() to readyz 200 with a connected daemon, not a stub.

2 new + 1 re-raise: CRF-15 re-raise (P3), CRF-22 (P3), CRF-21 (Nit), CRF-23 (Note). Event COMMENT. Reviewed against fe8af0a..c267018.

Cross-check notes:

  • CRF-15 re-raise: the fix suppressed the duplicate send (goroutine now does select{ case <-provReloadCtx.Done(): case <-s.daemon.Done(): default: send }) but did not remove the machinery. loadProviders returns non-nil only when daemon.Done() or provReloadCtx is already closed, so the default send (l369) and the serve receive arm (l384) are provably unreachable (0% coverage confirmed by multiple reviewers). 9 reviewers converged; spread Netero P2 / Bisky+Meruem+Hisoka+Mafu-san+Mafuuu+Pariston+Chopper+Razor P3 / Takumi+Komugi Note. Settled P3: the P2 driver (the CI flake) is genuinely gone; residual is dead code with a latent-reactivation trap (if loadProviders ever returns a non-daemon fatal error, the race CRF-15 named returns). Root-cause direction is to DELETE the channel + send + serve arm. Posted as a re-raise reply on the CRF-15 thread with thread_action unresolve.
  • CRF-21 (Gon P3 -> Nit): naming inconsistency (provReload* vs providerSync* for one goroutine). Downgraded to Nit for consistency with every prior naming finding in this PR (CRF-13/14/19 all Nit); pure naming, no invariant/regression impact. Keep-argument (comprehension cost from an incomplete rename) noted but weaker than the consistency bar.
  • CRF-22 (Leorio P3, kept): the runStandaloneGateway doc comment states connect-then-serve, contradicting the PR's core independence invariant (aibridged.New spawns go daemon.connect() and returns; serve binds regardless). Kept at P3 over the "just a comment" downgrade because it misstates the invariant on the PR's central function with a concrete regression path, distinct from a style nit.
  • CI red is external: PR diff builds/formats/vets and passes its Go + helm golden tests locally at head; the changes job failed first and the red spans unrelated checks (test-js, Storybook, sqlc-vet, offlinedocs). Reported in the body as a process note, not a diff finding.

Round 2 (cross-check)

Churn guard PROCEED; panel verified all 14 R1 findings genuinely fixed (loadProviders 100% cov, dead code removed, helper reuse restored, renames applied, comments/errors corrected). 6 new findings: 1 P2 (CRF-15), 1 P3 (CRF-20), 2 Note (CRF-17, CRF-18), 2 Nit (CRF-16, CRF-19). Event COMMENT. Reviewed against 27e7f4c..14c4113.

Cross-check notes:

  • CRF-15 convergence (Netero P2, Komugi P2, Meruem P3, +4 Note): the R2 CRF-3 fix added the initialProviderLoadErr channel/arm. On a terminal daemon failure both it and the daemon.Done() arm become ready, Go picks randomly, and the new TestRunStandaloneGateway_DaemonExited hard-asserts one of the two messages. Netero reproduced the flake empirically (~2/1000 at -count=1000 -cpu=8); Komugi reproduced it by inserting a 20ms sleep and confirmed the loaded-CI-runner losing schedule. This is the R1 Komugi "nondeterministic exit text" note (dropped R1 as cosmetic) now given teeth by a test that pins one branch. Two reviewers at P2 with empirical proof beat the Note framing; settled P2. Root-cause fix (give daemon.Done() priority / suppress initialProviderLoadErr when daemon is the cause) removes the flake, the dead arm, and the nondeterministic message together; a test-assertion-only fix papers over the dead arm.
  • CRF-3 stays "Author fixed": the join + listen/serve/daemon-exit arms are now covered and asserted (Chopper, Meruem verified). The residual 0%-coverage arm is the dead initialProviderLoadErr arm, split out as CRF-15. The other two uncovered arms (provReloadJoinErr timeout, httpServer.Close force-close) are defensive and expensive to drive; Meruem judged leaving them a reasonable tradeoff.
  • CRF-4 and CRF-6 shipped as documented tradeoffs: the author added the recommended values.yaml/field comments. Mafu-san verified the CRF-6 reconnect claim against reload.go/aibridgedserver.go (stale window bounded by reconnect + one reload).

Round 1

Netero-only gate cleared (P3 and below), panel proceeded. Panel: 16 trigger-matched (Bisky, Hisoka, Mafu-san, Mafuuu, Pariston, Gon, Leorio, Takumi, Meruem, Komugi, Chopper, Killua, Ryosuke, Robin, Zoro, ging-go) + 2 wildcards (Knov, Kite). Law skipped (effective additions 483 < 1000).

3 P3, 5 Note, 6 Nit. Event COMMENT. Reviewed against 27e7f4c..8b7d8c3.

Cross-check notes:

  • CRF-1: severity spread P2..Nit; settled P3. Code is correct today; this is a lost regression guard, not a live defect. run()'s own daemon.Done() select is a backstop. Verified the branch is genuinely reachable in production: aibridged connect() calls cancelFn (closing Done()) on terminal handshake errors (StatusBadRequest/Unauthorized/Forbidden = wrong key, incompatible API version, entitlement failure), aibridged.go:115-118. Mafu-san's P2 consequence (with liveness now default-on, a regressed branch yields a permanent healthy-looking zombie replica) recorded in the comment.
  • CRF-5 dead-code convergence: many reviewers independently traced WatchProviderReload (coderd/aibridged/reload.go) and confirmed it only returns ctx.Err() at runtime, so the escalation send cannot fire. Not a behavior change vs the old warn-and-continue, just misleading shape.
  • CRF-12: Gon rated P2; downgraded to Nit. No upward case: a partially-redundant (not misleading) comment has no runtime or maintenance consequence beyond verbosity.

Dropped (non-actionable "for the record" notes, no keep-argument): Gon section-label-comment note and retry magic-number note; Komugi nondeterministic-exit-error-text note (both branches set non-nil runErr; only the operator message varies) and RandomPort-race note (repo-wide sanctioned helper, negligible window); Robin test-helper-duplication note (author explicitly said current form works).

Process observation (body, not inline): commit subject is 79 chars (project caps at 72) and lowercases "http" where the body writes "HTTP" (Leorio Nit).

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a clean, well-motivated refactor. It decouples HTTP serving from the initial provider load so /healthz answers as soon as the listener binds, splits liveness (/healthz, process up) from readiness (/readyz, daemon connected plus providers loaded), and enables the Helm startup and liveness probes by default. The shutdown choreography is careful: the daemon is anchored to context.Background() so in-flight requests keep their DRPC connection while httpServer.Shutdown drains, and only then is the daemon closed. The timeout budget (300+5+5=310s) stays under terminationGracePeriodSeconds (330s), and TestStandaloneGatewayRun_ShutdownLifetimes proves the daemon outlives a draining request via channel handshakes rather than sleeps. Multiple reviewers ran the suite under -race with no concurrency findings.

The one theme worth acting on before merge is test coverage of the failure paths. The refactor deleted TestLoadProviders_AIBridgedDoneStopsRetry (and alwaysFailReloader), the only test exercising the daemon-death short-circuit in loadProviders; that branch survived verbatim but now runs at 0% coverage, and it is genuinely reachable in production (a wrong gateway key makes aibridged connect() close Done()). Separately, no test asserts run() ever returns a non-nil error, so the whole errors.Join aggregation and the serve-error path are unwitnessed. The happy paths are proven beautifully; the failure paths are not.

Severity count: 3 P3, 5 Note, 6 Nit. Nothing blocking.

Process note: the commit subject is 79 characters (the project caps subjects at 72) and lowercases "http" where the PR body writes "HTTP". Consider tightening and matching capitalization, e.g. "fix: start AI Gateway HTTP listener before initial provider load".

As Bisky put it: "you pried one stone out of the setting and left the empty prong."


helm/ai-gateway/values.yaml:198

Nit [CRF-11] The terminationGracePeriodSeconds comment names only the 300s HTTP timeout, but the budget now has three components. (Ryosuke)

This cross-file budget has no mechanical enforcement, so the comment is the only link.

The CLI comment now states the sum of shutdownTimeout + daemonShutdownTimeout + traceShutdownTimeout (310s), while this comment still reads "the binary's 300-second shutdown timeout plus termination headroom." Update it to name the daemon and trace timeouts too, so the next bump to any constant does not silently eat the 20s headroom.

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread enterprise/cli/aigatewaystart.go
Comment thread enterprise/cli/aigatewaystart.go
Comment thread enterprise/cli/aigatewaystart.go
Comment thread helm/ai-gateway/values.yaml
Comment thread enterprise/cli/aigatewaystart.go Outdated
Comment thread enterprise/cli/aigatewaystart.go Outdated
Comment thread enterprise/cli/aigatewaystart.go Outdated
Comment thread enterprise/cli/aigatewaystart.go Outdated
Comment thread enterprise/cli/aigatewaystart.go Outdated
Comment thread enterprise/cli/aigatewaystart.go Outdated
@pawbana

pawbana commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@pawbana
pawbana force-pushed the pawel/aigov-521-decouple-ai-gateway-healthz-from-initial-provider-load branch from 14c4113 to 015eae0 Compare July 21, 2026 17:12
@pawbana
pawbana force-pushed the pawel/aigov-318-create-helm-chart-for-standalone-ai-gateway branch from 27e7f4c to 9f4f825 Compare July 21, 2026 17:12
@pawbana

pawbana commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 2 verified. All 14 round-1 findings are genuinely fixed, not just claimed: the panel ran coverage and stress tests rather than trusting labels. loadProviders is back to 100% coverage with a real regression guard (TestStandaloneGatewayLoadProviders_DaemonDoneStopsRetry closes the daemon inside Reload, deterministically, no timing hack), the unreachable watch-escalation send is gone, both inline shutdowns route through shutdownWithTimeout again, the grace-period comment now itemizes the 300+5+5+20 budget, and the naming/doc fixes all landed. The restructure into runStandaloneGateway + serve reads well and the shutdown-ordering invariant (daemon outlives the HTTP drain) is now proven by TestStandaloneGatewayServe_ShutdownOrder.

One new issue is worth fixing before merge. The CRF-3 fix added an initialProviderLoadErr select arm that duplicates the daemon.Done() arm on the terminal-failure path: on a bad gateway key both arms become ready and Go picks one at random, so the process reports either "AI Gateway daemon exited" or "initialize ai providers". The new TestRunStandaloneGateway_DaemonExited hard-asserts one of the two, which turns that benign nondeterminism into a CI flake. Two reviewers reproduced it independently (Netero naturally at ~2/1000; Komugi deterministically by inserting a scheduling delay). This is the round-1 nondeterministic-exit-text note, dropped then as cosmetic, now given teeth by a test that pins one branch. Fixing only the assertion papers over the fact that the arm is dead machinery (0% coverage, can never deterministically win); the root-cause fix removes the flake, the dead arm, and the nondeterministic message at once.

Severity count (new this round): 1 P2, 1 P3, 2 Note, 2 Nit. Nothing blocking.

Process note: Coder squash-merges the PR title into main's permanent history, so the title matters. It is 79 characters (the project caps subjects at 72) and lowercases "http" where the body writes "HTTP"; consider tightening and matching casing, e.g. "fix(enterprise/cli): decouple AI Gateway HTTP listener from provider load".

As Bisky put it: "this round is lovely ... Real stones."

🤖 This review was automatically generated with Coder Agents.

Comment thread enterprise/cli/aigatewaystart.go Outdated
Comment thread enterprise/cli/aigatewaystart.go
Comment thread helm/ai-gateway/values.yaml
Comment thread enterprise/cli/aigatewaystart.go Outdated
Comment thread enterprise/cli/aigatewaystart.go Outdated
Comment thread enterprise/cli/aigatewaystart.go Outdated
@pawbana
pawbana force-pushed the pawel/aigov-318-create-helm-chart-for-standalone-ai-gateway branch from 9f4f825 to fe8af0a Compare July 22, 2026 11:11
@pawbana
pawbana force-pushed the pawel/aigov-521-decouple-ai-gateway-healthz-from-initial-provider-load branch from 015eae0 to c267018 Compare July 22, 2026 11:11
@pawbana

pawbana commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 3 verified. All six round-2 findings have genuine fixes, checked against the code rather than the labels. The CRF-15 flake is cured at the root, not re-shadowed: the load goroutine now suppresses the duplicate send, so daemon.Done() is the sole terminal reporter and TestRunStandaloneGateway_DaemonExited is deterministic (multiple reviewers ran it -race at counts of 300-500, clean). The CRF-18 shutdown budget is now split into four sequential timeouts (providerSync 5s + HTTP 300s + daemon 5s + trace 5s = 315s < 330s), and the const comment and values.yaml agree. CRF-20's new TestStandaloneGatewayHealthAndReadiness drives the real newGatewayMux/ready() composition to /readyz 200 with a genuinely connected daemon, then proves readiness drops on disconnect and recovers on reconnect. CRF-16/17/19 landed as claimed.

One round-2 finding is only half-resolved. The CRF-15 fix removed the flake and the nondeterministic message but suppressed the duplicate send instead of removing it, so initialProviderLoadErr, its send, and its serve arm are now provably unreachable dead code (0% coverage, confirmed by static proof: loadProviders only ever returns while daemon.Done() or the reload context is already closed). It is P3, not the original P2, because the CI flake is genuinely gone; the residual is dead machinery plus a latent-reactivation trap. Details on the re-raised thread. Two smaller new items: the central function's doc comment describes connect-then-serve (contradicting the independence this PR delivers), and the provider-reload goroutine is now named with two prefixes.

Severity count (this round): 2 P3, 1 Nit, 1 Note. Nothing blocking.

Process notes. (1) CI is broadly red, but the PR's own diff is clean locally at head: go build ./enterprise/cli/... ./cli/..., gofmt -l, go vet, the AI-gateway Go tests, and the helm ai-gateway golden tests all pass. The changes path-filter job failed first and the red spans checks this diff cannot affect (test-js, Storybook, sqlc-vet, offlinedocs), so the failure appears to originate outside this diff (the rebase/stack/base or CI infra); worth confirming against the job logs before merge. (2) Coder squash-merges the PR title into permanent history; it is 79 characters (the project caps subjects at 72) and lowercases "http" where the body writes "HTTP".

As Hisoka put it: "Round 2 left a corpse on the floor and the author only cleaned up the blood, not the body."

🤖 This review was automatically generated with Coder Agents.

Comment thread enterprise/cli/aigatewaystart.go Outdated
Comment thread enterprise/cli/aigatewaystart.go Outdated
Comment thread enterprise/cli/aigatewaystart.go Outdated
@pawbana
pawbana force-pushed the pawel/aigov-318-create-helm-chart-for-standalone-ai-gateway branch from fe8af0a to 12ca765 Compare July 22, 2026 14:06
@pawbana
pawbana force-pushed the pawel/aigov-521-decouple-ai-gateway-healthz-from-initial-provider-load branch 2 times, most recently from f7c4486 to 97feb9d Compare July 22, 2026 15:14
Base automatically changed from pawel/aigov-318-create-helm-chart-for-standalone-ai-gateway to main July 22, 2026 15:42
@pawbana
pawbana force-pushed the pawel/aigov-521-decouple-ai-gateway-healthz-from-initial-provider-load branch from 97feb9d to b989fe9 Compare July 22, 2026 16:57
@pawbana
pawbana requested a review from johnstcn July 22, 2026 17:17
@pawbana
pawbana marked this pull request as ready for review July 22, 2026 17:17

@johnstcn johnstcn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have any blocking concerns, just a few follow-up suggestions. 👍

Comment thread helm/ai-gateway/tests/testdata/default_values.golden
Comment on lines +39 to +42
// The sum of daemonShutdownTimeout, httpShutdownTimeout,
// providerReloadShutdownTimeout, and traceShutdownTimeout must stay below
// terminationGracePeriodSeconds in helm/ai-gateway/values.yaml so the
// process can complete graceful shutdown before Kubernetes sends SIGKILL.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may also be worth updating the relevant documentation regarding this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK, documentation being worked on in separate PR.

}

func (s *standaloneGateway) ready() bool {
return s.daemon.Ready() && s.providersLoaded.Load()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when we've completed the initial provider reload but subsequent reloads fail? Would it make sense to store both the last error and the last reload time, and emit those in the readiness endpoint? This could be a useful troubleshooting signal for operators.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first loading is special because without it AI Gateway will not serve any providers, have any LLM endpoints mounted, making it essentially useless.

Subsequent reload failing "only" makes providers outdated. The reload happens though DRPC connection which if dropped will make Gateway go into not ready state.
The only remaining edge case is that reload fails while DRPC connection remains active, which should be rare, but I agree that some mechanism that would make Gateway not ready after X failed reloads or X time passed since last successful sync would be nice.
I think WatchProviderReload implementation would make it a bit hard to keep track since it retries internally.

Comment on lines +503 to +512
select {
case <-dialCtx.Done():
case <-testCtx.Done():
t.Fatal("daemon dial context was not canceled")
}
select {
case <-daemon.Done():
case <-testCtx.Done():
t.Fatal("daemon did not stop")
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: testutil.RequireReceive

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, used testutil.TryReceive.

Comment on lines +520 to +527
require.Eventually(t, func() bool {
conn, err := net.Dial("tcp", address)
if err != nil {
return false
}
_ = conn.Close()
return true
}, testutil.WaitShort, testutil.IntervalFast)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: testutil.Eventually

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, changed.

Comment on lines +77 to +82
type controlledShutdownPool struct {
*aibridged.CachedBridgePool
err error
release <-chan struct{}
started chan<- struct{}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion, non-blocking: We could use github.com/coder/quartz to trap the shutdown process instead. But could be a follow-up.

@pawbana pawbana Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created followup issue

err error
after func()
called chan struct{}
type failingReloader struct {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion, non-blocking: would it make sense to auto-generate a mock for this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like auto generated mocks, maybe I need to get used to them but they make code ugly, and long IMO 😅
I agree some more generic mock struct would simplify, eg. failingReloader could simply be failThenSucceedReloader with large failUntil.

@pawbana
pawbana merged commit 468b1a2 into main Jul 23, 2026
29 checks passed
@pawbana
pawbana deleted the pawel/aigov-521-decouple-ai-gateway-healthz-from-initial-provider-load branch July 23, 2026 09:55
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 23, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants