Repository navigation
fix(download): save every download byte for byte whatever its Content-Type - #5
Conversation
…-Type Downloads ran through Restish's response formatter. When the server sent a request archive part as text/event-stream or NDJSON, Restish decoded it as a stream and handed the events to the capture formatter, which drops them for downloads, so cchp saved an empty file and reported ok: true. Downloads now ask Restish for the raw body (no output format), which it writes byte for byte for every Content-Type; the process transport records the response head. A response whose schema is a binary string is a download whatever its media types. An empty download of a part that the request archive lists as not empty fails with server_error. Tests download JSON, SSE client and attempt frames, octet-stream, NDJSON, JSONL and plain-text parts, a genuinely empty part and an error response from a server that behaves like CCHP, and compare each saved file's length and sha256 with what curl receives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8afae8c15d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| req := engine.Request{Op: op, Retry: op.Policy.Retry, HeaderTimeout: o.Timeout, MaxEvents: o.MaxEvents, DryRun: o.DryRun, | ||
| RawBody: op.Response.Kind == catalog.ResponseBinary} |
There was a problem hiding this comment.
Keep result metadata off raw download stdout
When a caller uses --output -, this binary path writes the response bytes to a.io.Out, but runOperation subsequently calls emit; redirected stdout automatically enables agent mode, so contract.Write appends a JSON result to the downloaded bytes (and terminal mode can append status/request metadata). This affects the newly classified downloadRequestArchiveBundle as well as the raw archive-part behavior being fixed here, so the command's stdout is not the byte-for-byte file asserted by the new test; suppress result emission for this mode or send metadata to a different stream.
Useful? React with 👍 / 👎.
Sync spec/upstream/control-plane.yaml to CCHP v1.0.10 (b0bf159de) with make sync-spec and regenerate the catalog, schemas, engine spec and docs/coverage.md: 621 operations, 514 available, 107 excluded with reasons, 0 unsupported. - 62 new operations get names, groups, English summaries and policies; nine are excluded with reasons. - listProviders became listAdminProviders; cchp providers overview keeps its name. - listModerationViolations and streamModerationViolations are removed with cchp safety violations and watch-violations. - Newly paged lists get pagination policies and new parameters get English help. - The e2e pagination test creates four providers. - CHANGELOG: v0.1.2 with the Unreleased download fixes (#5) and this sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
cchp requests download-part <request-id> 'client%2Fresponse.frames' --output out.ssereturnedok: true,file.bytes: 0,content_type: text/event-stream, andout.ssewas empty, while curl with the same token and URL received the whole part. The same happened forattempt%2F<n>%2Fresponse.frames. JSON parts such asclient%2Frequest.bodyworked.Downloads ran through Restish's response formatter. For a
text/event-stream(or NDJSON) response Restish takes its stream path and hands each event to the formatter (FormatValue); cchp's capture formatter drops events for downloads, sores.Rawstayed empty andsaveDownloadwrote 0 bytes and reported success.Change
cchp-genclassifies a response whose schema is a binary string as a download whatever its media types. CCHP serves an archive part with the part's own type (application/json,text/event-stream,application/x-ndjson,application/octet-stream).operation-policy.yamlgainslisting: forgetUsageLogArchiveRawit namesgetUsageLogArchive. When a download arrives empty, cchp reads the listing; if the listing shows the part as not empty the command fails withserver_error(exit 10), neverok: true.--output -writes the raw bytes to stdout.downloadRequestArchiveBundle(binary schema underapplication/json) becomes a download as well and needs--output.Tests
internal/runner/download_test.goruns the real runner and engine against an HTTP server that behaves like CCHP's archive endpoints, and compares each saved file with whatcurlreceives:application/jsontext/event-streamtext/event-stream; charset=utf-8application/octet-streamapplication/x-ndjson,application/jsonltext/event-stream,text/plain--output -text/event-streamapplication/jsontext/event-streamserver_error, exit 10application/problem+jsoncmd/cchp-gen/response_test.gocovers the response classification.Also run against a real CCHP gateway (feature branch of claude-code-hub-plus) with a stored request archive of 10 parts: v0.1.1 saved all 6
response.framesparts as empty files withok: true; this branch saves all 10 parts with the same length and sha256 as curl.go test -race ./...,go vet ./...,go vet -tags e2e ./...,golangci-lint run ./...andgo run ./cmd/cchp-gen -checkpass locally.🤖 Generated with Claude Code