From 2dce81fbf62c6767fd297b235ccc3fdd7db4f118 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Sun, 3 May 2026 18:34:48 -0700 Subject: [PATCH] Add optional bridge proxy request logging via PAPERCLIP_BRIDGE_DEBUG (#5140) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - Agents on remote sandboxes call back into the Paperclip control plane via a > callback bridge — the host process running the bridge proxies HTTP requests > from the sandbox to the Paperclip API > - When something goes wrong end-to-end (sandbox can't reach Paperclip, requests > timing out, malformed responses), it's hard to tell whether the bridge > processed the request, what URL/method it used, and what the upstream > responded with > - There was no built-in way to log bridge proxy traffic without modifying > adapter code or attaching a debugger > - This PR adds opt-in stdout logging of every bridge proxy request and response > (method, path, query, status), gated behind `PAPERCLIP_BRIDGE_DEBUG` so it > stays off by default > - The benefit is that operators can flip a single env var to get full visibility > into bridge traffic when debugging remote runs, without changing code ## What Changed - `packages/adapter-utils/src/execution-target.ts`: `startAdapterExecutionTargetPaperclipBridge`'s `handleRequest` now logs each proxied request and response when `PAPERCLIP_BRIDGE_DEBUG` is truthy: - `[paperclip] Bridge proxy ?` before fetch - `[paperclip] Bridge proxy response for ?` after - Logging is no-op when the env var is unset/`"0"`/`"false"`. ## Verification - Set `PAPERCLIP_BRIDGE_DEBUG=1` in the host process env, run an agent against a sandbox-backed environment, confirm the bridge log lines appear in stdout. - Unset the env var and confirm no extra log lines appear during normal runs. ## Risks - Off-by-default, no observable change for shipping users. - When enabled, the logging is verbose — every API call from the sandbox produces 2 stdout lines. Operators should only enable it during active debugging. ## Model Used - OpenAI GPT-5.4 (reasoning effort: high) via Codex CLI - Provider: OpenAI - Used to author the code changes in this PR ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [ ] I have added or updated tests where applicable — covered by exercising the flag in dev; the underlying handleRequest behavior is unchanged - [ ] If this change affects the UI, I have included before/after screenshots — N/A - [ ] I have updated relevant documentation to reflect my changes — N/A - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --- .../adapter-utils/src/execution-target.ts | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/adapter-utils/src/execution-target.ts b/packages/adapter-utils/src/execution-target.ts index e0b02730..6372e08d 100644 --- a/packages/adapter-utils/src/execution-target.ts +++ b/packages/adapter-utils/src/execution-target.ts @@ -125,6 +125,11 @@ function resolveDefaultPaperclipApiUrl(): string { return `http://${runtimeHost}:${runtimePort}`; } +function isBridgeDebugEnabled(env: NodeJS.ProcessEnv): boolean { + const value = env.PAPERCLIP_BRIDGE_DEBUG?.trim().toLowerCase(); + return value === "1" || value === "true" || value === "yes"; +} + function isAdapterExecutionTargetInstance(value: unknown): value is AdapterExecutionTarget { const parsed = parseObject(value); if (parsed.kind === "local") return true; @@ -743,11 +748,25 @@ export async function startAdapterExecutionTargetPaperclipBridge(input: { timeoutMs: adapterExecutionTargetTimeoutMs(target), shellCommand, }); + // PAPERCLIP_BRIDGE_DEBUG opts into verbose stdout logs of every bridge + // proxy request/response. The query string is logged verbatim, so callers + // who pass auth tokens or other sensitive values as query parameters + // should be aware those values appear in the host process's stdout when + // this flag is enabled. Only intended for active debugging in trusted + // environments. + const bridgeDebugEnabled = isBridgeDebugEnabled(process.env); worker = await startSandboxCallbackBridgeWorker({ client, queueDir, maxBodyBytes, handleRequest: async (request) => { + const method = request.method.trim().toUpperCase() || "GET"; + if (bridgeDebugEnabled) { + await onLog( + "stdout", + `[paperclip] Bridge proxy ${method} ${request.path}${request.query ? `?${request.query}` : ""}\n`, + ); + } const headers = new Headers(); for (const [key, value] of Object.entries(request.headers)) { if (value.trim().length === 0) continue; @@ -755,13 +774,18 @@ export async function startAdapterExecutionTargetPaperclipBridge(input: { } headers.set("authorization", `Bearer ${hostApiToken}`); headers.set("x-paperclip-run-id", input.runId); - const method = request.method.trim().toUpperCase() || "GET"; const response = await fetch(buildBridgeForwardUrl(hostApiUrl, request), { method, headers, ...(method === "GET" || method === "HEAD" ? {} : { body: request.body }), signal: AbortSignal.timeout(30_000), }); + if (bridgeDebugEnabled) { + await onLog( + "stdout", + `[paperclip] Bridge proxy response ${response.status} for ${method} ${request.path}${request.query ? `?${request.query}` : ""}\n`, + ); + } return { status: response.status, headers: buildBridgeResponseHeaders(response),