mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
Add optional bridge proxy request logging via PAPERCLIP_BRIDGE_DEBUG (#5140)
## 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 <METHOD> <path>?<query>` before fetch
- `[paperclip] Bridge proxy response <status> for <METHOD>
<path>?<query>` 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
This commit is contained in:
parent
0e51fa2b0d
commit
2dce81fbf6
1 changed files with 25 additions and 1 deletions
|
|
@ -125,6 +125,11 @@ function resolveDefaultPaperclipApiUrl(): string {
|
||||||
return `http://${runtimeHost}:${runtimePort}`;
|
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 {
|
function isAdapterExecutionTargetInstance(value: unknown): value is AdapterExecutionTarget {
|
||||||
const parsed = parseObject(value);
|
const parsed = parseObject(value);
|
||||||
if (parsed.kind === "local") return true;
|
if (parsed.kind === "local") return true;
|
||||||
|
|
@ -743,11 +748,25 @@ export async function startAdapterExecutionTargetPaperclipBridge(input: {
|
||||||
timeoutMs: adapterExecutionTargetTimeoutMs(target),
|
timeoutMs: adapterExecutionTargetTimeoutMs(target),
|
||||||
shellCommand,
|
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({
|
worker = await startSandboxCallbackBridgeWorker({
|
||||||
client,
|
client,
|
||||||
queueDir,
|
queueDir,
|
||||||
maxBodyBytes,
|
maxBodyBytes,
|
||||||
handleRequest: async (request) => {
|
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();
|
const headers = new Headers();
|
||||||
for (const [key, value] of Object.entries(request.headers)) {
|
for (const [key, value] of Object.entries(request.headers)) {
|
||||||
if (value.trim().length === 0) continue;
|
if (value.trim().length === 0) continue;
|
||||||
|
|
@ -755,13 +774,18 @@ export async function startAdapterExecutionTargetPaperclipBridge(input: {
|
||||||
}
|
}
|
||||||
headers.set("authorization", `Bearer ${hostApiToken}`);
|
headers.set("authorization", `Bearer ${hostApiToken}`);
|
||||||
headers.set("x-paperclip-run-id", input.runId);
|
headers.set("x-paperclip-run-id", input.runId);
|
||||||
const method = request.method.trim().toUpperCase() || "GET";
|
|
||||||
const response = await fetch(buildBridgeForwardUrl(hostApiUrl, request), {
|
const response = await fetch(buildBridgeForwardUrl(hostApiUrl, request), {
|
||||||
method,
|
method,
|
||||||
headers,
|
headers,
|
||||||
...(method === "GET" || method === "HEAD" ? {} : { body: request.body }),
|
...(method === "GET" || method === "HEAD" ? {} : { body: request.body }),
|
||||||
signal: AbortSignal.timeout(30_000),
|
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 {
|
return {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
headers: buildBridgeResponseHeaders(response),
|
headers: buildBridgeResponseHeaders(response),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue