Skip to content

gateway: tolerate non-string values in Bifrost log metadata (fixes 502'd rollups) - #1697

Merged
Evanfeenstra merged 1 commit into
mainfrom
gateway/tolerant-log-metadata
Sep 15, 2026
Merged

Evanfeenstra merged 1 commit into
mainfrom
gateway/tolerant-log-metadata

Conversation

@Evanfeenstra

Copy link
Copy Markdown
Collaborator

What broke

Every spend/histogram rollup in the gateway UI started returning 502 on 2026-09-15 (`docker logs bifrost.sphinx`):

ERROR adminapi: spend.by_agent_user: logstore: decode /api/logs?...: json: cannot unmarshal bool into Go struct field logstoreLog.logs.metadata of type string

Bifrost's log `metadata` is a `map[string]interface{}` on the wire (`framework/logstore/tables.go`). The dim headers we stamp are strings, but Bifrost's own logging plugin writes bool markers on some rows (`plugins/logging/main.go`):

  • `"realtime": true` on realtime turns
  • `"isAsyncRequest": true` on `x-bf-async` jobs

The plugin decoded metadata straight into `map[string]string`, so one such row on a page failed the whole `/api/logs` decode, and every rollup built on `windowedLogs` (by_agent, by_user, by_agent_user, histogram.cost, sessions, users) 502'd for as long as that row sat inside the window. Nothing in Bifrost changed (still pinned at `transports/v1.6.2`); a realtime turn simply landed in the logstore for the first time (the realtime WS path was being exercised earlier today, before #1695 shipped the wrapper block). Retention is 365d, so the row is not going away on its own.

Fix

Decode metadata through a `metadataMap` type with a tolerant `UnmarshalJSON`:

  • strings kept verbatim
  • bool / number values kept as their JSON text (`"true"`, `"42"`) so they stay visible in detail views and usable as filters
  • nested objects/arrays and nulls dropped
  • a non-object `metadata` is still a decode error

Applied to both the list row (`logstoreLog`) and the detail row (`logstoreLogDetail`). All existing indexing (`l.Metadata["run-id"]`) and the `map[string]string` output structs are untouched (named map → unnamed identical underlying type is assignable). No tygo drift: both types are unexported.

Verify

`go test ./internal/adminapi/` passes, including the new `TestMetadataMap_ToleratesNonStringValues`. `make tygo-check` clean.

To see the offending row on the swarm (from inside the network, with the same creds the plugin uses):

curl -s -u "$BIFROST_ADMIN_USER:$BIFROST_ADMIN_PASS" 'http://127.0.0.1:8080/api/logs?limit=1000&start_time='"$(date -u -d '24 hours ago' +%FT%TZ)" | grep -o '"realtime":true\|"isAsyncRequest":true' | sort | uniq -c

Release

Needs a new image (v0.4.264): publish a GitHub release once merged, same as #1696.

…2'd rollups)

Bifrost's logging plugin stamps bool markers into a row's metadata:
`realtime: true` on realtime turns and `isAsyncRequest: true` on
x-bf-async jobs (plugins/logging/main.go). The plugin decoded
metadata straight into map[string]string, so a single such row in
the window failed the whole /api/logs page and every rollup built on
it (spend.by_agent, by_user, by_agent_user, histogram.cost) returned
502 — the dashboard went blank on 2026-09-15 after a realtime turn
landed in the 24h window.

Decode metadata through a metadataMap type instead: strings verbatim,
bool/number values as their JSON text, nested and null values dropped.
Applied to both the list row and the detail row.
@Evanfeenstra
Evanfeenstra merged commit ebab867 into main Sep 15, 2026
9 checks passed
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.

1 participant