🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

feat: push provider changes to gateways via WatchAIProviders - #26746

Closed
dannykopping wants to merge 3 commits into
graphite-base/26746from
pawel/aigov-465-publish-provider-seed-completion-message
Closed

feat: push provider changes to gateways via WatchAIProviders#26746
dannykopping wants to merge 3 commits into
graphite-base/26746from
pawel/aigov-465-publish-provider-seed-completion-message

Conversation

@dannykopping

@dannykopping dannykopping commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Adds a WatchAIProviders streaming RPC to the ProviderConfigurator service so a running standalone AI Gateway refetches its provider set when the provider configuration changes. The server subscribes to AIProvidersChangedChannel (published by the provider CRUD endpoints) and forwards each event as a payload-free signal, plus one signal on subscribe; the gateway calls GetAIProviders on each signal to rebuild its pool. The aibridged API is bumped to v1.2.

Env-seeded providers don't need a signal: seeding finishes before coderd serves the gateway connection, so the gateway's initial fetch already reflects the seeded set. A periodic idempotent reload on the pool reloader is intentionally out of scope here and tracked separately.

Refs https://linear.app/codercom/issue/AIGOV-465/publish-provider-seed-completion-message-after-seedaiprovidersfromenv

Depends on #26605

…Providers

Add a WatchAIProviders streaming RPC to the ProviderConfigurator service so
a running standalone AI Gateway refetches its provider set when the provider
configuration changes. The server forwards AIProvidersChangedChannel events
(published by the provider CRUD endpoints) as payload-free signals; the
gateway refetches via GetAIProviders on each signal. Bumps the aibridged API
to v1.2.
@linear-code

linear-code Bot commented Jun 26, 2026

Copy link
Copy Markdown

AIGOV-465

dannykopping commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-06-26 15:13 UTC by @dannykopping
Spend: $47.50 / $100.00

Review history
  • R1 (2026-06-26): 15 reviewers, 3 Nit, 3 P3, 1 P4, COMMENT. Review
  • R2 (2026-06-26): 8 reviewers, 3 Nit, 4 P3, 1 P4, COMMENT. Review

deep-review v0.9.0 | Round 2 | 44b11df..40c6317

Last posted: Round 2, 8 findings (4 P3, 1 P4, 3 Nit), COMMENT. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P3 Author fixed (40c6317) coderd/aibridged/reload_test.go:107 No test for reload error non-fatality in WatchProviderReload R1 Bisky P3, Chopper P3 Yes
CRF-2 P3 Author fixed (40c6317) coderd/aibridgedserver/aibridgedserver.go:799 Goroutine to link contexts replaceable by context.AfterFunc R1 Ging-Go Yes
CRF-3 P3 Author fixed (40c6317) coderd/aibridged/reload.go:120 Warn log on reload failure reads like success R1 Leorio Yes
CRF-4 P4 Author fixed (40c6317) enterprise/cli/aigatewaystart.go:132 Fire-and-forget goroutine not tracked for shutdown sync R1 Ryosuke P3, Meruem Note Yes
CRF-5 Nit Author fixed (40c6317) coderd/aibridgedserver/aibridgedserver.go:796 Comments restate code across production and test R1 Gon Yes
CRF-6 Nit Author fixed (40c6317) coderd/aibridgedserver/aibridgedserver.go:31 Import alias coderpubsub diverges from dominant convention coderdpubsub R1 Gon Yes
CRF-7 Nit Author fixed (40c6317) coderd/aibridgedserver/aibridgedserver.go:818 Pubsub callback silently discards delivery error without logging R1 Razor Yes
CRF-8 P3 Open enterprise/cli/aigatewaystart.go:143 Defer ordering deadlocks shutdown on HTTP server error when daemon disconnected R2 Pariston Yes

Round log

Round 1

Panel. 3 P3, 1 P4, 3 Nit. Reviewed against 44b11df..3b6f968.

Round 2

Churn guard: PROCEED. 7/7 addressed. Panel: 1 P3 new (CRF-8). Reviewed against 44b11df..40c6317.

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.

@dannykopping dannykopping changed the title feat(coderd/aibridged): push provider changes to gateways via WatchAIProviders feat: push provider changes to gateways via WatchAIProviders Jun 26, 2026
@pawbana
pawbana changed the base branch from pawel/aigov-315-implement-basic-coder-aibridge-start-sub-command to graphite-base/26746 June 26, 2026 14:31

@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.

Clean streaming RPC addition with solid test coverage (53% test density). The signal-on-subscribe pattern that closes the race window between loadProviders and the watch stream is well-considered. The coalescing buffer with non-blocking write is textbook, and the error-signals-as-refetch-triggers is strictly better than the embedded path's skip-on-error behavior.

3 P3, 1 P4, 3 Nit. No blockers.

Multiple reviewers independently noted the asymmetry where SubscribeProviderReload (embedded path, pre-existing) skips reload on pubsub delivery errors, while WatchAIProviders correctly signals. The new code does it right; the pre-existing embedded path would benefit from the same fix in a follow-up.

"The one asymmetry I noticed: SubscribeProviderReload skips reloads on pubsub drop errors, while the new WatchAIProviders callback signals on all events including drops. The new code's behavior is strictly better." (Hisoka)

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/aibridged/reload_test.go
Comment thread coderd/aibridgedserver/aibridgedserver.go Outdated
Comment thread coderd/aibridged/reload.go Outdated
Comment thread enterprise/cli/aigatewaystart.go
Comment thread coderd/aibridgedserver/aibridgedserver.go Outdated
Comment thread coderd/aibridgedserver/aibridgedserver.go Outdated
Comment thread coderd/aibridgedserver/aibridgedserver.go Outdated
- Use context.AfterFunc to bind stream lifecycle to the server (CRF-2)
- Log pubsub delivery errors in the watch callback (CRF-7)
- Clarify the reload-failure warn log (CRF-3)
- Drain the standalone gateway watch goroutine before srv.Close() (CRF-4)
- Add a test that a failed reload does not stop the watch loop (CRF-1)
- Rename import alias coderpubsub to coderdpubsub (CRF-6)
- Drop comments that restate the code (CRF-5)

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.

All 7 R1 findings addressed cleanly in 40c6317. The context.AfterFunc replacement (CRF-2), WaitGroup addition (CRF-4), and new TestWatchProviderReloadContinuesAfterReloadError test (CRF-1) are all well-executed.

1 new P3 from the R2 panel. The CRF-4 fix (WaitGroup) introduced a defer ordering issue that can deadlock the process on the HTTP server error path.

"If the watch goroutine is inside Client() waiting for a daemon reconnection, watchCancel() does nothing because Client() only checks lifecycleCtx, not the watch context. watchWG.Wait() blocks. srv.Close() never executes. Deadlock." (Pariston)

🤖 This review was automatically generated with Coder Agents.

providerLogger.Warn(watchCtx, "ai provider watch loop exited", slog.Error(err))
}
}()
defer func() {

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.

P3 [CRF-8] Defer ordering can deadlock shutdown on the HTTP server error path.

LIFO defer execution order:

  1. listener.Close()
  2. watchCancel(); watchWG.Wait()
  3. srv.Close()

watchWG.Wait() (step 2) runs before srv.Close() (step 3). srv.Close() cancels lifecycleCtx, which is the only context Server.Client() blocks on (aibridged.go:137-141). If the watch goroutine is inside Client() waiting for a daemon reconnection when the HTTP server returns an error, watchCancel() cancels watchCtx but Client() doesn't observe it. watchWG.Wait() hangs. srv.Close() never runs. Process hangs until an external SIGTERM cancels ctx (the parent of lifecycleCtx).

Trigger: HTTP server error (rare) while the daemon has lost its coderd connection (watch goroutine stuck in Client()). Both must coincide.

The comment at line 132 ("The deferred cancel+wait drains this goroutine before srv.Close() runs at shutdown") is correct for the normal signal path (ctx canceled -> lifecycleCtx canceled -> Client() unblocks) but wrong for the serveErr path.

Fix: cancel lifecycleCtx before waiting, by calling srv.Close() inside the watch defer. shutdownOnce makes the second srv.Close() from the earlier defer idempotent:

defer func() {
    watchCancel()
    srv.Close()    // Cancel lifecycleCtx so Client() unblocks.
    watchWG.Wait()
}()

(Pariston)

🤖

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.

Fixed in c931be3. The deferred drain now calls srv.Close() (which cancels the daemon lifecycle context that Client() blocks on) before watchWG.Wait(), so a watch goroutine stuck reconnecting unblocks on the HTTP server error path. Close is idempotent via shutdownOnce, so the earlier deferred srv.Close is a no-op.

srv.Close cancels the daemon lifecycle context, the only context
WatchProviderReload's blocking Client() call observes. Call it before
watchWG.Wait() so a watch goroutine waiting to reconnect unblocks on the HTTP
server error path instead of hanging. Close is idempotent (CRF-8).
@dannykopping

Copy link
Copy Markdown
Contributor Author

Superseded by #26797 (branch renamed to danny/aigov-465-provider-reload). Closing this one.

@dannykopping
dannykopping deleted the pawel/aigov-465-publish-provider-seed-completion-message branch June 29, 2026 08:39
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 29, 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.

1 participant