cloudflare / cloudflare/mcp-server-cloudflare

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

Open Beginner friendly
#418 3 comments 2 reactions 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
4.2k
Forks
514
Avg merge
1d 21h
Merged PRs (30d)
2

Description

## 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.ts` — `zCloudflareMiniEvent` (line ~251):

```ts
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:

```ts
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):

```jsonc
{
"$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: "" }
]},
timeframe: { from: "", 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:

```ts
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 (#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.

Contributor guide

Open the contributing guide

Research direction

Start with zCloudflareMiniEvent in packages/mcp-common/src/types/workers-logs.types.ts and trace responseSchema.parse in packages/mcp-common/src/cloudflare-api.ts. Reproduce the failure with a console.log event lacking outcome, then verify that query_worker_observability returns those events instead of discarding the whole response; consider the issue's per-event safeParse and required-field audit suggestions separately.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.