Skip to content

fix(observability): report the model that actually ran on the AI failure path, and surface degraded requests - #10192

Merged
JSONbored merged 1 commit into
mainfrom
fix/ai-model-label-10186
Jul 31, 2026
Merged

fix(observability): report the model that actually ran on the AI failure path, and surface degraded requests#10192
JSONbored merged 1 commit into
mainfrom
fix/ai-model-label-10186

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

$ai_model reported a model that never ran on every failed AI call.

resolveModel exists because the core passes a Workers-AI model id that is meaningless off-Workers — it discards any @cf/-prefixed value and substitutes the provider's own. The success path captured usage.model (the real resolved model, correct); the failure path had no usage to read and fell back to the raw argument, i.e. exactly the placeholder resolveModel had thrown away.

The result was a perfect false signal. On the live project all 33 failed AI events were mislabelled: a breakdown by model showed a set of Cloudflare model ids at a 100% error rate for a deployment that has never called Workers AI, while the models that were actually failing — the claude-code CLI and self-hosted ollama — read 0%, because their failures were attributed elsewhere. This is the same class of bug the codebase already fixed once in a different sink (src/services/ai-review.ts: "self-host claude-code reviews were mis-logged as the Workers-AI model ids, which hid outages").

The fix

Each adapter now reports what its own run() would resolve, through a new optional telemetryModel on SelfHostAi. In every adapter the resolution is hoisted into one expression that both run() and telemetryModel call, so the label and the actual request cannot drift. The OpenAI-compatible adapter branches on the same options.text discriminator run does, so an embedding request is labelled with the embed model (bge-m3:latest) rather than the chat model it never touches.

runProviderWithOtel and withAiGenerationCapture resolve it once, before the call, and use it on both paths. usage.model still wins on success — it is what the provider reported actually running — and the two now agree for a given configured provider. The OTel span attribute carried the identical lie and reads from the same value.

Consequences, per the issue's deliverables: a @cf/ value can no longer reach $ai_model from the self-host path, and an unconfigured provider resolves to <provider>-default instead of a bare "default" that named no provider at all (codex resolves to "" on purpose — the CLI picks the account default — so codex-default keeps that attributable).

Degraded requests that reached no model

Two paths left nothing in PostHog, so a provider failing or being skipped for every request looked exactly like a provider with no traffic:

  • circuit-breaker skip — emitted only a Prometheus counter (on the ORB's own stack) and threw. Once the breaker latches, that provider's $ai_generation stream simply stops.
  • chain exhaustion — wrote a console line, which reaches PostHog only as an $exception, an event the AI observability views do not read.

Both now emit selfhost_ai_degraded, carrying the shared $ai_trace_id so a degraded request joins the real attempts around it in the same trace. Deliberately not an $ai_generation with $ai_is_error: true: no model ran, so booking it against one would credit that model with a failure it never had — the same false signal this issue is about — and would drag zero-token, zero-cost rows into the spend aggregates.

A CLI-subscription provider declining an embed (claude_code_no_embed) is routing working as designed, not a degradation, and stays exempt at both sinks — one exemption rule rather than two that can disagree. A chain with no embed-capable member at all is a real misconfiguration, but its fail-loud signal is shouldWarnRagEmbedUnavailable's boot warning, not one event per chunk batch.

Closes #10186

Scope

  • The PR title follows type(scope): short summary Conventional Commit format, for example fix(api): restore profile access checks.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked a currently open issue this PR resolves (e.g. Closes #123) — a linked open issue is required for every contributor PR.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally; codecov/patch requires ≥99% coverage of the lines AND branches you changed (aim for 100% on your diff so CI variance does not fail near the threshold). Global coverage is a non-blocking trend with a loose 90% backstop, not the gate.
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

If any required check was skipped, explain why:

  • Coverage was measured scoped to the two changed source files rather than via a full test:coverage run: 100% of the changed lines AND branches in src/selfhost/ai.ts and src/selfhost/posthog.ts are covered, verified line-by-line against the lcov report rather than read off a summary percentage. ui:lint was run as prettier --check on the one changed MDX file (clean).
  • The unchecked commands cover untouched surfaces — this diff changes no workflow, worker binding, OpenAPI schema, or MCP package manifest — and are left to CI.

Tests added

Four existing tests asserted the old "default" label, which is precisely the behaviour the issue declares wrong; they were updated to the corrected expectation with the reasoning inline, not deleted. On top of that:

  • The issue's own stated invariant: the success and failure paths report the same model for the same configured provider.
  • A @cf/ placeholder never reaches $ai_model — with an adapter that implements telemetryModel, and with one that does not (where it must still degrade to <provider>-default rather than pass through).
  • An adapter's telemetryModel outranks a truthy non-@cf/ model argument (the live visual-vision case), and a blank one is ignored.
  • An embedding request is labelled with the embed model, driven through the real createOpenAiCompatibleAi adapter and a 400 response.
  • circuit_open after the breaker latches, asserting the skip is not also booked as a generation.
  • chain_exhausted naming every provider tried and attributing to the one that ran last, with both per-provider failures still captured individually alongside it.
  • The exemption in both directions: an all-declined embed chain captures nothing; a decline mixed with one real failure does capture.
  • An empty chain, covering both the passed-model and the "unknown" fallback.
  • capturePostHogAiDegradation directly, for every branch — inactive client, trace present/absent, providers absent/empty/non-empty, error present/absent, Error/non-Error, blank provider/model, repo group present/absent, and that no content field is ever attached.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests.
  • API/OpenAPI/MCP behavior is updated and tested where needed.
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks.
  • Visible UI changes include a UI Evidence section below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails. SVG screenshots are not used as review evidence. Review-only screenshots or recordings are not committed to the repository.
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs.

The new event carries the same metadata-only posture as $ai_generation: provider/model ids, a reason, and an already-redacted error string. The repo group reuses the value operationalProperties already processed, so under the shared central key it inherits HMAC anonymization and the fail-closed drop for free — a raw private repo name cannot reach the group index by this path. A test asserts no content field is ever attached.

UI Evidence

Not applicable — no visible UI or frontend change. The one docs change is a prose/FeatureRow addition to the existing self-hosting operations page, whose accuracy is pinned by the drift test rather than by a screenshot.

Notes

apps/loopover-ui/content/docs/self-hosting-operations.mdx documented the AI observability surface and would have become false: it now describes the failure-path model guarantee and the new selfhost_ai_degraded event, and the drift test pins the exported event name and both reasons against the code — the same guard the monitor-heartbeat event already has, so the page cannot silently drift from what is emitted.

The $ai_trace_id/repo-group logic #10187 introduced was extracted into two small shared helpers so the new event lands in the same trace and inherits the same fail-closed grouping, rather than carrying a second copy of that fallback that could drift.

…ure path, and surface degraded requests

resolveModel discards the Workers-AI model id the core passes, because it is
meaningless off-Workers. The success path captured usage.model (correct), but the
failure path had no usage to read and fell back to the raw argument -- the very
placeholder resolveModel threw away. Every failed AI call was therefore labelled
with a model that never ran: on the live project all 33 failures reported `@cf/*`
ids at a 100% error rate for a deployment that has never called Workers AI, while
the real failing providers (the claude-code CLI, ollama) read 0%.

Each adapter now reports, via a new optional telemetryModel on SelfHostAi, exactly
what its own run() would resolve -- derived from the same hoisted expression, so
the label and the request cannot drift. runProviderWithOtel and
withAiGenerationCapture resolve it once, up front, and use it on both paths; the
OTel span attribute carried the identical lie and reads from the same value. A
`@cf/` id can no longer reach telemetry, and an unconfigured provider resolves to
`<provider>-default` rather than a bare "default" that named no provider at all.

Two degradation paths also left nothing in PostHog, so a provider failing or being
skipped for every request looked exactly like a provider with no traffic: a
circuit-breaker skip emitted only a Prometheus counter, and chain exhaustion only a
console line (an $exception the AI views do not read). Both now emit
selfhost_ai_degraded, carrying the shared $ai_trace_id so they join the real
attempts around them. Deliberately not an $ai_generation: no model ran, so none is
credited with the failure and the spend/token aggregates stay a record of real
calls. A CLI provider declining an embed is routing working as designed and stays
exempt at both sinks.

Closes #10186
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 31, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
loopover-ui 7051e8c Jul 31 2026, 12:01 PM

@JSONbored JSONbored self-assigned this Jul 31, 2026
@loopover-orb

loopover-orb Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Important

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏳ LoopOver is waiting…

LoopOver has seen this pull request and is waiting on CI checks to finish before reviewing it. This comment will update once the review runs.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed · 🟨 Waiting

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 1.76kB (0.02%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
loopover-ui 7.96MB 1.76kB (0.02%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-EYkH8spU.js (New) 2.16MB 2.16MB 100.0% 🚀
assets/tanstack-vendor-CL-34Gak.js (New) 956.91kB 956.91kB 100.0% 🚀
assets/docs.fumadocs-spike-api-reference-BBMZyuv5.js (New) 443.45kB 443.45kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-B0m7TGP_.js (New) 201.7kB 201.7kB 100.0% 🚀
assets/modal-CjRvc1Wl.js (New) 184.5kB 184.5kB 100.0% 🚀
assets/client-gHM-vD8t.js (New) 151.47kB 151.47kB 100.0% 🚀
assets/self-hosting-operations-DHg80WDa.js (New) 136.08kB 136.08kB 100.0% 🚀
assets/maintainer-panel-BdZaSggc.js (New) 78.97kB 78.97kB 100.0% 🚀
assets/routes-SFRHtKe1.js (New) 35.88kB 35.88kB 100.0% 🚀
assets/owner-panel-DoPR5tqm.js (New) 28.42kB 28.42kB 100.0% 🚀
assets/app-BK9IH_Yt.js (New) 25.82kB 25.82kB 100.0% 🚀
assets/ui-vendor-Bnof2WdV.js (New) 24.57kB 24.57kB 100.0% 🚀
assets/fairness-Ccw3ll6X.js (New) 23.44kB 23.44kB 100.0% 🚀
assets/miner-panel-Bn9kPN0Z.js (New) 20.49kB 20.49kB 100.0% 🚀
assets/api._op-D4AOiwMo.js (New) 17.51kB 17.51kB 100.0% 🚀
assets/self-hosting-docs-audit-lH3aAQNn.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs-client-loader-KQwdercn.js (New) 15.68kB 15.68kB 100.0% 🚀
assets/playground-panel-Czp1aVIb.js (New) 14.35kB 14.35kB 100.0% 🚀
assets/app.audit-Dar9KtCs.js (New) 10.22kB 10.22kB 100.0% 🚀
assets/app.config-generator-CRiOwcs-.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-CJbCkq7_.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-DLUoxWxu.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-DEU0pPAK.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/proof._owner._repo-CKFjpkKE.js (New) 7.11kB 7.11kB 100.0% 🚀
assets/commands-panel-Cuuczjgo.js (New) 6.74kB 6.74kB 100.0% 🚀
assets/maintainer-workflow-wXDz7Qpg.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/docs-nav-C49mNMEX.js (New) 6.19kB 6.19kB 100.0% 🚀
assets/digest-panel-CZyU55T0.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-DMgyVXvI.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs.index-BK4Muhcc.js (New) 6.01kB 6.01kB 100.0% 🚀
assets/api.index-D-Aryqo7.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-dMdcPZ2o.js (New) 3.01kB 3.01kB 100.0% 🚀
assets/api-lOqDXh7w.js (New) 2.66kB 2.66kB 100.0% 🚀
assets/docs-page-CgpmFjX7.js (New) 2.14kB 2.14kB 100.0% 🚀
assets/table-Rl_mg1ze.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-BnZUDdjD.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-DanE0kri.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-D3RgYqO4.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-DwspPsn5.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-C1Mb4xbl.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/docs._slug-CKwE6t66.js (New) 554 bytes 554 bytes 100.0% 🚀
assets/app.maintainer-BwLkkDGm.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-BtpI79cM.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-uIKueTqS.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-DnQDbUaw.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-BDK3-v9-.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-6i1Y4yhG.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-kHlEDhgn.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-Cje5J1fG.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-CVuvmfaq.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-CAd4FTYF.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/git-pull-request-arrow-VoTdj4Ki.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-DLfyJV8R.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-lJWtW84f.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-DxWnLVoA.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-CjtdNyLg.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-CaHoF2NS.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/play-sm-2Wzh7.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-COpZTflA.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/add-scalar-classes-0l-QgAgn.js (Deleted) -2.16MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-D1XwWJaJ.js (Deleted) -956.91kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-Ds99qGLD.js (Deleted) -443.45kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-CeAyCLMZ.js (Deleted) -201.7kB 0 bytes -100.0% 🗑️
assets/modal-CouvvIYd.js (Deleted) -184.5kB 0 bytes -100.0% 🗑️
assets/client-C3n_sI9I.js (Deleted) -151.47kB 0 bytes -100.0% 🗑️
assets/self-hosting-operations-jdhwnSpO.js (Deleted) -134.31kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-BSqFq0DZ.js (Deleted) -78.97kB 0 bytes -100.0% 🗑️
assets/routes-CQjSrrwj.js (Deleted) -35.88kB 0 bytes -100.0% 🗑️
assets/owner-panel-C48HZ5SO.js (Deleted) -28.42kB 0 bytes -100.0% 🗑️
assets/app-CZ34BXKA.js (Deleted) -25.82kB 0 bytes -100.0% 🗑️
assets/ui-vendor-CxViubyP.js (Deleted) -24.57kB 0 bytes -100.0% 🗑️
assets/fairness-Bnb9foYC.js (Deleted) -23.44kB 0 bytes -100.0% 🗑️
assets/miner-panel-D-8_Rseo.js (Deleted) -20.49kB 0 bytes -100.0% 🗑️
assets/api._op-Dxiqgi_y.js (Deleted) -17.51kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-Bow__fFG.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs-client-loader-DKUYrsMC.js (Deleted) -15.68kB 0 bytes -100.0% 🗑️
assets/playground-panel-DCPZR96b.js (Deleted) -14.35kB 0 bytes -100.0% 🗑️
assets/app.audit-HZlgU2yY.js (Deleted) -10.22kB 0 bytes -100.0% 🗑️
assets/app.config-generator-DqRal5aO.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-D225onJE.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-gR4VRyDb.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-DTL4HyvJ.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/proof._owner._repo-BJs4Ykwa.js (Deleted) -7.11kB 0 bytes -100.0% 🗑️
assets/commands-panel-CDeSO6vB.js (Deleted) -6.74kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-BQWQ_aYn.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/docs-nav-l0HCmZi8.js (Deleted) -6.19kB 0 bytes -100.0% 🗑️
assets/digest-panel-CPqs4edK.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-x1HSSHa4.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs.index-xiRhe8D9.js (Deleted) -6.01kB 0 bytes -100.0% 🗑️
assets/api.index-D0tpw-hu.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs-aoM2z4G_.js (Deleted) -3.01kB 0 bytes -100.0% 🗑️
assets/api-BDOxqxpj.js (Deleted) -2.66kB 0 bytes -100.0% 🗑️
assets/docs-page-GQ7iN_ij.js (Deleted) -2.14kB 0 bytes -100.0% 🗑️
assets/table-D08cwXAg.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-B46ZpdmX.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-DEBbUosX.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-D0BdOXar.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-sZyv2IqO.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-BOUOUOek.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/docs._slug-BDcjHpc-.js (Deleted) -554 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-Dma_MvJJ.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-DYuaiEzL.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-ZI3C6-Z2.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-BC5ixK69.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-CJfpSRRU.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-Q7sZtmAd.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-Bb_JLBJv.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-V-RbWat6.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-esAegovZ.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-Dvx5h6ZV.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-PI-JPzDb.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-DSknAt6x.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-C-Gx7tGA.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-XGt6AMZC.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-Cx9_o6ie.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-CCX09DGk.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/play-Cfcfp3p0.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-v9dSU7wJ.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️

@JSONbored
JSONbored merged commit 96e6394 into main Jul 31, 2026
6 of 8 checks passed
@JSONbored
JSONbored deleted the fix/ai-model-label-10186 branch July 31, 2026 12:04
@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
25877 1 25876 21
View the top 1 failed test(s) by shortest run time
test/integration/api.test.ts > api routes > serves private MCP tool listing and tool calls
Stack Traces | 0.647s run time
AssertionError: expected 500 to be 200 // Object.is equality

- Expected
+ Received

- 200
+ 500

 ❯ test/integration/api.test.ts:5098:44

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ai(observability): $ai_model reports the discarded Workers-AI placeholder on every failed call, hiding which model actually broke

1 participant