Skip to content

metadata.refresh() always fails: "Expected object, received string at metadata" (GET /api/v1/runs/{id}/metadata returns serialized metadata) #5039

Open

Description

@anthony-sbl

Summary

metadata.refresh() inside a task always rejects with:

200 Validation error: Expected object, received string at "metadata"

Versions

  • @trigger.dev/sdk / @trigger.dev/core 4.5.12. The client code in 4.7.4 and on main looks the same.
  • Trigger.dev Cloud.

Reproduction

  1. Run a task that sets any metadata, for example metadata.set("key", "value") and then await metadata.flush().
  2. In the same task, call await metadata.refresh(). It throws the error above.
  3. Calling the endpoint directly shows why:
curl -H "Authorization: Bearer $TRIGGER_SECRET_KEY" https://api.trigger.dev/api/v1/runs/<runId>/metadata

returns metadata as a serialized JSON string, alongside metadataType: "application/json". In contrast, GET /api/v3/runs/<runId> (runs.retrieve()) returns metadata as an object.

Where it seems to come from

  • Server: the GET loader in apps/webapp/app/routes/api.v1.runs.$runId.metadata.ts, added in 89431c7, returns the stored metadata value as-is, without deserializing it. The PUT handler in the same file returns a parsed object.
  • Client: getRunMetadata (packages/core/src/v3/apiClient/index.ts) validates the response with UpdateMetadataResponseBody, which expects metadata to be a record.

Workaround

Read the metadata back with runs.retrieve(runId) instead of metadata.refresh().

Activity

  1. lakshya-dhariwal commented on Oct 19, 2026

    @lakshya-dhariwal

    Confirmed the root cause and have a tested fix.

    The GET loader in apps/webapp/app/routes/api.v1.runs.$runId.metadata.ts returns the stored packet string as-is at all three read points (replica, mollifier buffer, primary), while runs.retrieve deserializes through parsePacketAsJson (ApiRetrieveRunPresenter.server.ts) and the PUT handler in the same file returns the parsed object. So the SDK's UpdateMetadataResponseBody (metadata: z.record(...)) always rejects the GET response.

    Suggested fix, verified locally: deserialize in the loader with parsePacketAsJson({ data, dataType: metadataType ?? "application/json" }) at the three return points, return {} when metadata is absent (matching the PUT service's empty case in updateMetadata.server.ts), and pass through a buffered snapshot that already holds an object. I added route tests for the PG / buffered / object-passthrough / empty cases and updated the existing replica-lag guard test for the parsed shape: 13/13 pass with the fix, and the new tests fail against the unpatched route.

    Happy to open a PR with it if a maintainer is willing to vouch (CONTRIBUTING gates PRs on that).

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions