Skip to content

Workers Observability: required outcome field discards entire response (46% of real events, 86% of cron) #418

Description

@ipols

Problem

query_worker_observability requires $workers.outcome to be a string on every returned event. Because the response is validated with .parse() over the whole array, a single event without outcome throws away the entire response — the tool returns no logs at all.

outcome (ok / exception / canceled) describes a whole invocation. Cloudflare attaches it to the invocation-summary line ($metadata.type = "cf-worker-event"). Individual console.log lines emitted inside an invocation ($metadata.type = "cf-worker") are not invocations and legitimately have no outcome. The API is behaving correctly; the schema's assumption that every event is an invocation is what's wrong.

This is the same class of bug as #362 (schema too strict for a heterogeneous event stream). The workflow enum value and the cpuTimeMs/wallTimeMs fields named there appear to have been addressed since, but outcome is the remaining — and highest-impact — instance of the pattern.

Root cause

packages/mcp-common/src/types/workers-logs.types.tszCloudflareMiniEvent (line ~251):

export const zCloudflareMiniEvent = z.object({
  event: zCloudflareMiniEventDetails,
  scriptName: z.string(),
  outcome: z.string(),        // <-- required, but absent on every console.log line
  eventType: z.enum([...]),
  requestId: z.string(),
  ...
})

packages/mcp-common/src/cloudflare-api.ts (line ~67) parses the whole payload and throws on the first failure:

if (responseSchema) {
  return responseSchema.parse(data)   // one bad event => entire response lost
}

Because zCloudflareEvent extends zCloudflareMiniEvent, both branches of the union require outcome, which is why the surfaced error is invalid_union with path: ["outcome"] in both branches.

Measured impact

Measured against 1,816 real production events from one Worker over one day (2026-07-16), grouped by $metadata.type:

events has outcome missing outcome
total 1,816 976 (53.7%) 840 (46.3%)
cron 680 96 584 (86%)
fetch 1,136 880 256

The split is exactly along $metadata.type: all 976 cf-worker-event rows have outcome; all 840 cf-worker rows do not.

It fails hardest on cron logs — 86% missing — which is exactly what you need to read during an outage.

Reproduced live against a 35-minute production window containing a real incident: the tool returned zero events and ~30k characters of Zod errors. Every one of the 5 diagnostic console.log events needed to explain that incident was discarded, because all 5 were cf-worker lines with no outcome. Querying the same window over the raw REST endpoint returns all 84 events.

Reproduction

Any Worker that calls console.log() inside a request or scheduled handler will reproduce this. Minimal synthetic event that fails validation (this is the shape the API returns for a console.log line inside a cron invocation):

{
  "$workers": {
    "event": { "cron": "*/15 * * * *", "scheduledTime": 1784222146000 },
    "scriptName": "my-worker",
    "eventType": "cron",
    "executionModel": "stateless",
    "requestId": "1RI7X6A7OCMC159U"
    // no "outcome" — this is correct API output, but fails zCloudflareMiniEvent
  }
}

Then:

query_worker_observability({
  view: "events",
  queryId: "repro",
  limit: 100,
  parameters: { datasets: ["cloudflare-workers"], filters: [
    { key: "$metadata.service", operation: "eq", type: "string", value: "<worker>" }
  ]},
  timeframe: { from: "<any window with console.log output>", to: "..." }
})

Result: Error analyzing worker logs: [ ... "path": ["outcome"], "message": "Invalid input: expected string, received undefined" ... ] repeated per offending event, and no logs returned.

Suggested fix

Make the field optional — it is genuinely absent for a whole legitimate class of event:

outcome: z.string().optional(),

Two adjacent hardening suggestions, worth more than the one-line fix on its own:

  1. Don't fail the batch for one row. Validate events individually (.safeParse() per event) and return the ones that pass, ideally reporting how many were dropped. A logs tool that returns 99% of the data plus a warning is far more useful during an incident than one that returns nothing.
  2. Audit the other required fields on this schema for the same assumption. event, scriptName and requestId are also declared required on zCloudflareMiniEvent; anything not guaranteed on non-invocation rows will cause the identical whole-response failure. This is the third report of that pattern (Workers Observability: eventType 'workflow' not supported in schema #362, and now this).

Happy to open a PR for the one-line change if that's useful — say the word and I'll send it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions