Repository navigation
feat(plugin): implement chunked fetch for large responses and add tests - #694
Conversation
There was a problem hiding this comment.
ℹ️ No critical issues — one minor suggestion inline.
Reviewed changes
- Chunked
paca.fetch— new host-sidefetchChunkStore(fetch_chunks.go) parks an oversized response body and returnsbody_handle/body_size; a follow-upfetchwith{body_handle, offset, limit}streams base64 chunks and releases the handle after the final read. Bounded at 2 handles per plugin with a 60s TTL and a 1 MiB raw cap per chunk. - Explicit
response_too_large—writeJSONResultCheckedsurfaces allocator failures;fetch,settings_get, anddb_query2now return an actionable error envelope (withcode: "response_too_large") instead of a silent empty response. - provider-cli config sync — new
persistedCLIEntriesrewrites$HOME-relative config paths onto the real persistent-volume path (HomeRoot/<Name()>) and the tar is extracted from/, avoiding the~/.claudeabsolute-symlink resolution bug; the persisted ancestors are excluded from generated directory headers. - Tests —
runtime_fetch_test.go(arena boundary, chunk reassembly/retry/release, store TTL + eviction) andprovider_cli_sync_test.go.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…r-plugin Resolves the add/add conflict in services/api/internal/platform/plugin/runtime_fetch_test.go: master's file (private-network host and redirect guard tests, #693) is kept as is, and the chunked-fetch tests move to runtime_fetch_chunk_test.go.
There was a problem hiding this comment.
ℹ️ No new issues — the delta since the last review is the
mastermerge, which is clean. The earlier minor wording suggestion is still open, so this review stays non-approving to leave it actionable.
Reviewed changes
- Merged
masterinto the branch, threadingr.services.FetchPrivateHostsintoisAllowedFetchDomain(runtime.go:2276) so the chunk-aware fetch handler keeps master's private-network and redirect protections. - Renamed the PR's test file from
runtime_fetch_test.gotoruntime_fetch_chunk_test.goto avoid colliding with master's same-named file; no duplicate top-level symbols, and the package compiles and passes. - Confirmed the chunk read path has no new behavioral surface:
req.BodyHandle != ""returns before domain validation, and a handle only exists after the fetch that parked it already passed the domain, private-host, and redirect checks — and the store is per-plugin.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
The shared response-too-large message told every caller to "request less data (e.g. a smaller page size)", which a plugin cannot do for a third-party paca.fetch response. The message now takes a remedy per operation: db_query and settings_get keep the page-size hint, a fetch that did not opt in is told to repeat the request with "chunked": true, a chunk read is told to use a smaller limit, and a chunked fetch whose headers alone do not fit says so. Addresses the review comment on #694.
…hunked-body-fetching-for-plugin
a8b3c79 to
7df45f3
Compare
There was a problem hiding this comment.
✅ No new issues found. The only prior open thread is now addressed.
Reviewed changes
- Pointed
paca.fetchsize errors at the escape hatch:responseTooLargeMessagenow takes a per-operationremedy, so a non-chunked oversized fetch saysrepeat the request with "chunked": trueinstead of suggesting a smaller page, whiledb_query/settings_getkeeprequest less data (e.g. a smaller page size). - Differentiated the chunked fetch failures: a chunked fetch whose metadata itself does not fit falls through to
remedyHeaders(the response headers alone are too large to deliver), and an oversizedbody_handleread usesremedySmallerChunk(read it again with a smaller limit) — the right remedy for each path. - Added
TestResponseTooLargeMessage_NamesTheRemedyForEachOperationand extended the fetch/chunk assertions to pin the remedy wording (fetch no longer mentions a smaller page).
This addresses the one open inline suggestion from the prior review; the thread has been resolved.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Summary
Two fixes that make agents and plugins work with large Git data:
paca.fetchresponses. A response whose JSON envelope did not fit a plugin's fixed 1 MiB buffer came back as an empty response, which plugins reported as a network failure ("GitHub is unreachable"). The host now delivers it in chunks, or says plainly that it does not fit.issue_list, no task or repo tools).Part of Paca-AI/plugin-sdk-go#16. The SDK half is Paca-AI/plugin-sdk-go#18, released as
plugin-sdk-gov0.4.2.1. Chunked fetch and explicit size errors (
services/api/internal/platform/plugin)Problem. Host results are written into the guest's fixed 1 MiB arena through
paca_malloc. When a fetch envelope did not fit,writeJSONResultreturned{0,0}; even the error message could not be delivered, so the guest saw an empty response. A real GitHub issue-list page (1.1 MB) hit this.Changes
fetch_chunks.go(new): a per-plugin store of response bodies parked for chunked reads (at most 2 unread bodies, 60 s TTL, 1 MiB per chunk).paca.fetchaccepts"chunked": true. If the envelope does not fit, the host parks the body and answers with the status, headers, abody_handleandbody_size. The plugin then reads{body_handle, offset, limit}pieces; each chunk is base64 in the JSON envelope. The handle is released after the last chunk is delivered, and a chunk that does not fit leaves the handle in place so the caller can retry with a smallerlimit.chunked(older SDKs), an oversized response now returns a small explicit error envelope,{"status":0,"error":"…does not fit…","code":"response_too_large"}, not an empty response.db_query2(through its existing error pointer) andsettings_get, so a zero-length result there can no longer be mistaken for "no rows" or "no settings". New helpers:writeJSONResultChecked,responseTooLargeMessage,errCodeResponseTooLarge.2. Provider-CLI config sync (
services/agent-runner/internal/executor)Problem. For a
provider_cliagent,syncProviderCLIConfigwrites the CLI's MCP config to~/.claude/.claude.json(and the Codex, Cursor and Gemini equivalents), where~/.<cli>is an absolute symlink onto the persistent volume. The tar was extracted at/home/gooseand relied on the extraction following that symlink. Docker 29.x resolves an absolute symlink against the extraction directory instead of the container root, so the file was written to/home/goose/home/paca/workspaces/.cli-home/<cli>/...and the CLI never read it. The runner logged nothing.Change.
persistedCLIEntriesmaps each file onto its real volume path (home/paca/workspaces/.cli-home/<cli>/...) and the tar is extracted from/, so no symlink is involved. The volume directories already exist (the bootstrap step creates them), so they are excluded from the generated directory entries and only genuinely new subdirectories (such asskills/<name>) get headers. This also fixes skill files synced the same way.Compatibility
chunkedflag, so an oversized response gets the explicitresponse_too_largeerror.chunkedis ignored and the failure message is the clearer "host returned no data".Notes
paca.fetchhosts and redirect guard (feat(plugins): allow private network hosts for paca.fetch #693) touch the same handler. Chunk reads only serve a body parked by a fetch that already passed the domain, private-host and redirect checks. Master'sruntime_fetch_test.gois kept as is; the chunking tests moved toruntime_fetch_chunk_test.go(both files were added with the same name).Tests
runtime_fetch_chunk_test.go, against the real wazero runtime and fetch handler with a small-arena fixture (testdata/fetchplugin, 64 KiB):response_too_large(the 1,048,535 / 1,048,536 boundary from the report);chunkedstill gets the explicit error;limit;provider_cli_sync_test.go: for all four adapters, every synced file is under the real volume path and not under the symlinked~/.<cli>, no directory header is written for existing directories, and nested skill files keep their subdirectories.go vet,golangci-lint(0 issues) andgo testforinternal/platform/pluginandinternal/executor/...pass.