Skip to content

fix(server): bound provider event log records and pass Codex images by path - #653

Merged
rynfar merged 2 commits into
pylonfrom
upstream/2026-09-18-provider-payload-bounds
Sep 18, 2026
Merged

rynfar merged 2 commits into
pylonfrom
upstream/2026-09-18-provider-payload-bounds

Conversation

@rynfar

@rynfar rynfar commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

Ports two upstream server fixes from T3 Code. Part of upstream integration cycle #650, bounded at upstream 93e04160a0c0dece8a258384d2118a660415a53d.

Sources

Source Upstream Outcome
b17cc2ab5d5029f898121798ca19db3288cba616 #12305 bound provider event log records before serialization Partially adopted
3fd21df62da3191f1abf12a0cd74df6de9ed30a2 #11050 pass Codex image attachments by path to avoid oversized requests Adopted

Bounded provider event log records

A provider event with a cyclic reference, a pathological nesting depth or a very large payload could previously drive unbounded traversal inside the NDJSON logger's serialization, and an event that serialized successfully was written at whatever size it happened to be.

boundProviderEventForLogging now walks a record before encoding it, with a 64 KiB character budget, a 1,024 field budget, a depth cap of 16 and a WeakSet ancestor check. Records that fit are written unchanged. Records that do not fall back to summarizeProviderEvent, which keeps routing and failure fields — provider, method, ids, status, error and stop reasons — while replacing long strings with { omittedCharacters } and arrays with { itemCount }. Escaping can still expand a bounded record past the budget, so serialization is re-checked by byte length and falls back to the summary a second time.

Also suppresses three more high-volume transient frames: turn/diff/updated, message.part.delta and Anthropic stream_event / content_block_delta.

Pylon's commitGuard on makeEventNdjsonLogStore.write and Pylon's existing transient filters for claude/stream_event/content_block_delta/, session/update and message.part.updated are preserved.

Excluded: raw-frame suppression

Upstream's change also drops native frames whose envelope carries stage: "raw", and unwraps stage: "decoded" payloads. That part is deliberately not ported.

Two reasons, both verified against this tree rather than assumed:

  • The guard is inert here. apps/server/src/provider/acp/AcpNativeLogging.ts writes protocol records as { observedAt, event: { …, payload: { direction, stage, payload } } }. Upstream reads envelope.stage, one level above where Pylon's stage actually sits, so the branch never fires. GrokSkills.ts is the only other stage emitter and its values are spawn/timeout/exit/decode.
  • The intent is wrong for Pylon even if it did fire. Upstream's rationale is that raw frames duplicate every token delta. Pylon's do not — formatProtocolLogPayload already reduces the payload through summarizePayload to { valueType, byteLength }, and protocol logging is only attached under verboseProtocolLogging, an explicit opt-in. Those records are the evidence that survives when a frame fails to decode or a response never arrives, which is the Antigravity ACP session/prompt hang signature behind feat: add ACP prompt liveness and truthful forced stops #609.

Revisit trigger: Pylon moves stage to the envelope level in AcpNativeLogging.ts, or ACP raw frames begin carrying unsummarized payloads.

Codex image attachments by path

Image attachments were read from disk and inlined into turn/start as a base64 data URL, so the JSON-RPC request grew with the file. They are now passed as { type: "localImage", path } and the CLI reads the file itself. localImage is a real content type in packages/effect-codex-app-server/src/_generated/schema.gen.ts, so this is protocol-valid rather than a hopeful rename.

Pylon's attachment-id validation against serverConfig.attachmentsDir still runs before the path is returned, so a path outside the attachment store is still rejected. Unlike upstream, FileSystem.FileSystem stays wired into makeCodexAdapter — Pylon's makeCodexSessionRuntime still requires it.

Behavior change worth noting

A circular or oversized event previously produced a SchemaError diagnostic and no log line; it now produces a bounded summary line. The existing Pylon test asserting the SchemaError path was replaced by one asserting the summary path, and it keeps the original assertion that the event's contents never reach the diagnostics. Summarization only narrows what is written — events under budget are unaffected.

Verification

Run on the final head, not taken from a report:

  • vp test run apps/server/src/provider/Layers/EventNdjsonLogger.test.ts apps/server/src/provider/Layers/CodexAdapter.test.ts apps/server/src/provider/Layers/CodexSessionRuntime.test.ts — 3 files, 133 tests passed.
  • vp run -F t3 typecheck — exit 0.
  • vp check on the six changed files — formatting and lint clean.

Backend-only; no client evidence required.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

juliusmarminge and others added 2 commits September 18, 2026 15:01
Port of upstream commit b17cc2ab5d5029f898121798ca19db3288cba616 (#12305).
…requests

Port of upstream commit 3fd21df62da3191f1abf12a0cd74df6de9ed30a2 (#11050).
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L labels Sep 18, 2026
@vercel

vercel Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
pylon-marketing Ready Ready Preview Sep 18, 2026 10:05pm UTC

@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 14.0 KiB 13.9 KiB −45 B (−0.3%) 15.1 KiB ✅
Codex Thread snapshot wire 7.2 KiB 7.2 KiB −7 B (−0.1%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.8 KiB 6.7 KiB −38 B (−0.5%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 58.0 KiB 58.0 KiB −44 B (−0.1%) 66.4 KiB ✅
Codex Live turn messages 10 9 −1 (−10.0%) 21 ✅
Claude Total thread wire 14.0 KiB 14.0 KiB −28 B (−0.2%) 15.1 KiB ✅
Claude Thread snapshot wire 7.2 KiB 7.2 KiB +10 B (+0.1%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.8 KiB 6.7 KiB −38 B (−0.5%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 58.9 KiB 58.9 KiB −44 B (−0.1%) 66.4 KiB ✅
Claude Live turn messages 10 9 −1 (−10.0%) 21 ✅

Baseline: 933d6f7 · PR result: 2a3bfa0 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 115.6 KiB
  • Claude decoded thread snapshot: 116.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@rynfar
rynfar merged commit 4be539f into pylon Sep 18, 2026
18 of 19 checks passed
@rynfar
rynfar deleted the upstream/2026-09-18-provider-payload-bounds branch September 18, 2026 21:22

This branch was previously deployed

1 inactive deployment
Preview — 2a3bfa07 Deployed Sep 18, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants