chore/formatting#787
Conversation
|
CodeAnt AI is reviewing your PR. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
| if (isRequestOptions(query)) { | ||
| return this.connect(sessionName, {}, query); | ||
| } | ||
| return this._client.get(`/pty/${sessionName}`, { query, ...options }); |
There was a problem hiding this comment.
Suggestion: The sessionName path parameter is interpolated directly into the URL without encoding. If it contains /, ?, #, or other reserved characters, requests will be routed to the wrong endpoint (or fail), so valid session names can become unreachable. Encode the path segment before building the URL in PTY endpoints. [api mismatch]
Severity Level: Major ⚠️
- ❌ PTY.connect fails for session names with reserved characters.
- ⚠️ Certain PTY sessions become unreachable via generated client.Steps of Reproduction ✅
1. In a consumer application, call the generated PTY client method `Pty.connect()` defined
in `src/resources/pty.ts:11-25` with a session name that includes a reserved character,
e.g. `sessionName = 'user1/foo'`.
2. The overload resolution in `Pty.connect()` falls through to the implementation at
`src/resources/pty.ts:17-25`, which calls `this._client.get(`/pty/${sessionName}`, {
query, ...options });` (line 25) with `sessionName` unchanged.
3. Inside the core API client, `APIClient.get()` in `src/core.ts:16-18` receives the path
string `/pty/user1/foo` and forwards it unchanged to `methodRequest()` at
`src/core.ts:36-49`, which constructs the HTTP request without any URL-encoding of the
`path` argument.
4. The HTTP request is sent to `/pty/user1/foo`, so the `sessionName` value is split into
multiple path segments by the `/`, and other reserved characters like `?` or `#` would be
interpreted as query or fragment delimiters; the server can no longer reconstruct the
original session name, causing lookups for sessions named `user1/foo` (or with similar
reserved characters) to be misrouted or return errors even though the session is valid.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/resources/pty.ts
**Line:** 25:25
**Comment:**
*Api Mismatch: The `sessionName` path parameter is interpolated directly into the URL without encoding. If it contains `/`, `?`, `#`, or other reserved characters, requests will be routed to the wrong endpoint (or fail), so valid session names can become unreachable. Encode the path segment before building the URL in PTY endpoints.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| const secretName = uniqueName('gateway-test-secret'); | ||
| const secretValue = 'test-value-from-gateway'; | ||
|
|
||
| expect(createResult.httpCode).toBe(200); | ||
| const created = JSON.parse(createResult.responseBody); | ||
| expect(created.name).toBe(secretName); | ||
| const createResult = await curlRequest('POST', '/v1/secrets', { | ||
| body: JSON.stringify({ name: secretName, value: secretValue }), | ||
| contentType: 'application/json', |
There was a problem hiding this comment.
Suggestion: This test creates a new secret but never deletes it, while suite cleanup only removes testSecretName. Repeated smoketest runs will accumulate orphaned secrets and can eventually fail due to account limits or name collisions. Add cleanup for this created secret in the same test (or track and delete in afterEach/afterAll). [missing cleanup]
Severity Level: Major ⚠️
- ⚠️ Smoketest suite leaks remote secrets on every successful run.
- ⚠️ Orphaned secrets may eventually exhaust secret or quota limits.Steps of Reproduction ✅
1. Ensure environment variables `RUN_SMOKETESTS` and `RUNLOOP_API_KEY` are set so the
describe block `'comprehensive gateway proxying tests'` in
`tests/smoketests/object-oriented/gateway-config.test.ts:41-207` is executed.
2. Run the smoketest suite (e.g., `pnpm test
tests/smoketests/object-oriented/gateway-config.test.ts`); the `beforeAll` at `line
92-138` creates a shared secret named `testSecretName` and the `afterAll` at `line
379-387` only deletes this `testSecretName` via `sdk.secret.delete(testSecretName)` (line
152-154).
3. Within that suite, the test `test.concurrent('POST request - create a secret', ...)` at
`line 182-194` executes the snippet at lines `422-427`, calling `curlRequest('POST',
'/v1/secrets', ...)` with a new `secretName = uniqueName('gateway-test-secret')` that is
distinct from `testSecretName`, creating a new remote secret when `createResult.httpCode`
is 200.
4. Observe that there is no corresponding deletion for this per-test `secretName` anywhere
in the file (verified via Grep for `gateway-test-secret` only at lines `422-427`);
re-running the smoketests repeatedly will accumulate orphaned secrets in the Runloop
workspace since suite teardown only deletes `testSecretName`.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** tests/smoketests/object-oriented/gateway-config.test.ts
**Line:** 422:427
**Comment:**
*Missing Cleanup: This test creates a new secret but never deletes it, while suite cleanup only removes `testSecretName`. Repeated smoketest runs will accumulate orphaned secrets and can eventually fail due to account limits or name collisions. Add cleanup for this created secret in the same test (or track and delete in `afterEach`/`afterAll`).
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| const blueprintName = uniqueName('gateway-test-blueprint'); | ||
|
|
||
| expect(createResult.httpCode).toBe(200); | ||
| const created = JSON.parse(createResult.responseBody); | ||
| expect(created.name).toBe(blueprintName); | ||
| const createResult = await curlRequest('POST', '/v1/blueprints', { | ||
| body: JSON.stringify({ | ||
| name: blueprintName, | ||
| system_setup_commands: ['echo "test"'], | ||
| }), | ||
| contentType: 'application/json', |
There was a problem hiding this comment.
Suggestion: This test creates a blueprint and does not delete it afterward. Because these resources persist outside the test process, repeated executions will leak blueprints and can break future runs when workspace quotas are hit. Add explicit teardown for the created blueprint ID before the test exits. [resource leak]
Severity Level: Major ⚠️
- ⚠️ Smoketests leak blueprint resources into the shared workspace.
- ⚠️ Accumulated blueprints can hit workspace or API quotas.Steps of Reproduction ✅
1. Ensure environment variables `RUN_SMOKETESTS` and `RUNLOOP_API_KEY` are set so the
`'comprehensive gateway proxying tests'` describe block in
`tests/smoketests/object-oriented/gateway-config.test.ts:41-207` is executed.
2. Run the smoketest suite (e.g., `pnpm test
tests/smoketests/object-oriented/gateway-config.test.ts`); `beforeAll` at `line 92-138`
provisions shared resources, and `afterAll` at `line 379-387` tears down only
`gatewayConfig`, `networkPolicy`, and the shared secret `testSecretName` via
`sdk.secret.delete(testSecretName)`.
3. The test `test.concurrent('POST request with JSON body - create blueprint', ...)` at
`line 196-210` executes the snippet at lines `436-443`, calling `curlRequest('POST',
'/v1/blueprints', ...)` with `blueprintName = uniqueName('gateway-test-blueprint')`, which
creates a new remote blueprint when `createResult.httpCode` is 200.
4. Verify that there is no corresponding deletion for this `blueprintName` in the file
(Grep for `gateway-test-blueprint` only finds lines `436-443`), and no generic blueprint
cleanup helper; repeated smoketest runs will therefore accumulate persistent blueprint
resources in the Runloop workspace until external cleanup or quota limits are hit.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** tests/smoketests/object-oriented/gateway-config.test.ts
**Line:** 436:443
**Comment:**
*Resource Leak: This test creates a blueprint and does not delete it afterward. Because these resources persist outside the test process, repeated executions will leak blueprints and can break future runs when workspace quotas are hit. Add explicit teardown for the created blueprint ID before the test exits.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| addSocketListener(ws, 'error', (event) => { | ||
| const error = event instanceof Error ? event : new Error(String(event)); | ||
| this.closeReject(error); | ||
| }); |
There was a problem hiding this comment.
Suggestion: Rejecting closedPromise on socket error can create unhandled promise rejections when callers use DevboxPtySession without awaiting waitForClose(). This can surface as process-level warnings or failures in strict environments. Convert this path to a controlled close/finish flow (or internally consume the rejection) so session errors do not become unhandled by default. [possible bug]
Severity Level: Major ⚠️
- ⚠️ WebSocket errors can emit unhandled promise rejection warnings.
- ⚠️ Interactive PTY users must defensively handle unused close promises.Steps of Reproduction ✅
1. A user creates an interactive PTY session via the SDK, for example by obtaining a
`Devbox` and calling `devbox.pty.open()` (Devbox exposes `pty: DevboxPtyOps` in
`src/sdk/devbox.ts:45-56`, and `DevboxPtyOps.open()` is implemented in
`src/sdk/pty.ts:299-317`), then registers output callbacks with
`DevboxPtySession.onOutput()` (`pty.ts:132-135`) but does not call `waitForClose()`.
2. `DevboxPtyOps.open()` constructs a `DevboxPtySession` (`src/sdk/pty.ts:91-110`), whose
constructor creates an internal `closedPromise` at `pty.ts:101-104`, storing its `resolve`
and `reject` callbacks as `closeResolve` and `closeReject`, and exposes it via
`waitForClose()` at `pty.ts:178-180`.
3. In the same constructor, the WebSocket `'error'` listener is attached at
`pty.ts:126-129`: `addSocketListener(ws, 'error', (event) => { const error = event
instanceof Error ? event : new Error(String(event)); this.closeReject(error); });`, so any
socket-level error after the session is established will reject the internal
`closedPromise`.
4. When such a WebSocket error occurs (e.g., connection reset or protocol error from the
PTY backend), the `'error'` handler rejects `closedPromise` while no caller is awaiting
`waitForClose()` or attaching a `.catch()`; this rejected promise (created in
`pty.ts:101-104`) then becomes an unhandled promise rejection at the process or browser
level, producing unhandled-rejection warnings or process termination in stricter Node
configurations, even though the user only used the session for streaming via callbacks.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/sdk/pty.ts
**Line:** 126:129
**Comment:**
*Possible Bug: Rejecting `closedPromise` on socket `error` can create unhandled promise rejections when callers use `DevboxPtySession` without awaiting `waitForClose()`. This can surface as process-level warnings or failures in strict environments. Convert this path to a controlled close/finish flow (or internally consume the rejection) so session errors do not become unhandled by default.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| const timer = setTimeout(() => { | ||
| if (settled) return; | ||
| settled = true; | ||
| ws.terminate?.(); | ||
| reject(new Error('PTY WebSocket connection timed out.')); | ||
| }, WS_CONNECT_TIMEOUT_MS); |
There was a problem hiding this comment.
Suggestion: The WebSocket connect-timeout path only calls terminate, which is specific to the ws package and absent on browser WebSockets. In browsers this leaves timed-out sockets open until the platform closes them, causing dangling connections/listeners. Add a close() fallback when terminate is unavailable. [resource leak]
Severity Level: Major ⚠️
- ⚠️ Stalled PTY connects leave unused WebSocket connections open.
- ⚠️ Retries can accumulate dangling listeners in browser sessions.Steps of Reproduction ✅
1. A client application uses PTY from the high-level SDK: `Devbox` exposes `public
readonly pty: DevboxPtyOps` in `src/sdk/devbox.ts:45-56`, and consumers obtain a session
via `devbox.pty.open()` or `devbox.pty.exec()` (which internally calls `open()` at
`src/sdk/pty.ts:299-317`).
2. In a browser environment, `openPtyWebSocket()` (`src/sdk/pty.ts:385-399`) calls
`openPtyWebSocketOnce()`, which resolves the WebSocket implementation with
`resolveWebSocket()` (`pty.ts:447-454`). Because `globalThis.WebSocket` exists in
browsers, `resolveWebSocket()` returns the native `WebSocket` and `supportsHeaders:
false`.
3. `openPtyWebSocketOnce()` then constructs a browser `WebSocket` via `new
WebSocketImpl(wsUrl, authToken ? [authToken] : undefined)` at `src/sdk/pty.ts:404-411` and
sets up a timeout using `const timer = setTimeout(() => { ... }, WS_CONNECT_TIMEOUT_MS);`
at `pty.ts:413-418` without attaching any additional browser-specific cleanup.
4. If the WebSocket handshake stalls (for example, due to a firewall, network issue, or a
server that never responds), no `'open'` or `'error'` event fires before
`WS_CONNECT_TIMEOUT_MS`, the timeout callback runs, marks the promise as settled, calls
`ws.terminate?.()` (a no-op on browser WebSockets which lack `terminate`) and rejects the
promise, but never calls `ws.close()`. The unresolved browser `WebSocket` continues
attempting to connect with listeners still attached, so each retry from
`openPtyWebSocket()` (`pty.ts:385-399`) leaves behind an extra in-flight connection until
the platform eventually times them out.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/sdk/pty.ts
**Line:** 413:418
**Comment:**
*Resource Leak: The WebSocket connect-timeout path only calls `terminate`, which is specific to the `ws` package and absent on browser WebSockets. In browsers this leaves timed-out sockets open until the platform closes them, causing dangling connections/listeners. Add a `close()` fallback when `terminate` is unavailable.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| function decodeWebSocketBytes(data: unknown): Uint8Array { | ||
| if (typeof data === 'string') return Buffer.from(data, 'utf8'); | ||
| if (Buffer.isBuffer(data)) return data; | ||
| if (data instanceof ArrayBuffer) return new Uint8Array(data); | ||
| if (Array.isArray(data)) return Buffer.concat(data as Buffer[]); | ||
| if (data instanceof Uint8Array) return data; | ||
| return Buffer.from(String(data ?? ''), 'utf8'); |
There was a problem hiding this comment.
Suggestion: decodeWebSocketBytes uses Buffer unconditionally for string/array payloads, but Buffer is not guaranteed in browser runtimes (which this SDK supports). In web environments without a Buffer polyfill, the first PTY message can throw ReferenceError: Buffer is not defined and break streaming. Use TextEncoder/Uint8Array fallback when Buffer is unavailable. [type error]
Severity Level: Critical 🚨
- ❌ Browser PTY sessions can crash on first text message.
- ⚠️ Devbox.pty unusable in web clients without Buffer polyfill.Steps of Reproduction ✅
1. In a browser/web runtime, a user imports the web shims (`src/shims/web.ts:1-5`), which
configure fetch/streams but do not define a global `Buffer` (no Buffer-related exports
anywhere in that file).
2. The user creates a Devbox instance and accesses PTY operations via the SDK: `Devbox`
exposes `public readonly pty: DevboxPtyOps` in `src/sdk/devbox.ts:26-56`, and
`DevboxPtyOps` is exported from `src/sdk/index.ts:1-11`, so a typical app calls
`devbox.pty.open()` or `devbox.pty.exec()`.
3. `DevboxPtyOps.open()` in `src/sdk/pty.ts:299-317` creates a WebSocket with
`openPtyWebSocket()` (`pty.ts:385-399`), then constructs a `DevboxPtySession`
(`pty.ts:91-110`), which sets `this.ws.binaryType = 'arraybuffer'` and attaches a
`'message'` listener at `pty.ts:111-122` that calls
`decodeWebSocketBytes(readEventData(event))`.
4. When the PTY backend sends any text WebSocket frame (for example, a JSON control
message or error string), the browser `WebSocket` delivers `event.data` as a string;
`readEventData()` in `pty.ts:500-507` returns that string, and `decodeWebSocketBytes()` at
`pty.ts:491-497` executes `Buffer.from(data, 'utf8')` for string payloads even though
`Buffer` is not defined in the browser, causing a `ReferenceError: Buffer is not defined`
and breaking PTY output handling for that session.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/sdk/pty.ts
**Line:** 491:497
**Comment:**
*Type Error: `decodeWebSocketBytes` uses `Buffer` unconditionally for string/array payloads, but `Buffer` is not guaranteed in browser runtimes (which this SDK supports). In web environments without a Buffer polyfill, the first PTY message can throw `ReferenceError: Buffer is not defined` and break streaming. Use `TextEncoder`/`Uint8Array` fallback when `Buffer` is unavailable.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| function decodeWebSocketBytes(data: unknown): Uint8Array { | ||
| if (typeof data === 'string') return Buffer.from(data, 'utf8'); | ||
| if (Buffer.isBuffer(data)) return data; | ||
| if (data instanceof ArrayBuffer) return new Uint8Array(data); | ||
| if (Array.isArray(data)) return Buffer.concat(data as Buffer[]); | ||
| if (data instanceof Uint8Array) return data; | ||
| return Buffer.from(String(data ?? ''), 'utf8'); |
There was a problem hiding this comment.
🔴 Architect Review — CRITICAL
PTY WebSocket message decoding assumes the global Buffer API is always available; in browser and edge runtimes where WebSocket exists but Buffer does not, handling a message will throw at runtime and break the PTY session.
Suggestion: Make decodeWebSocketBytes runtime-safe by guarding on typeof Buffer !== 'undefined' and falling back to Uint8Array/TextDecoder-based handling, matching the existing cross-runtime byte-handling pattern so PTY works in non-Node runtimes the SDK supports.
Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is an **Architect / Logical Review** comment left during a code review. These reviews are first-class, important findings — not optional suggestions. Do NOT dismiss this as a 'big architectural change' just because the title says architect review; most of these can be resolved with a small, localized fix once the intent is understood.
**Path:** src/sdk/pty.ts
**Line:** 491:497
**Comment:**
*CRITICAL: PTY WebSocket message decoding assumes the global Buffer API is always available; in browser and edge runtimes where WebSocket exists but Buffer does not, handling a message will throw at runtime and break the PTY session.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
If a suggested approach is provided above, use it as the authoritative instruction. If no explicit code suggestion is given, you MUST still draft and apply your own minimal, localized fix — do not punt back with 'no suggestion provided, review manually'. Keep the change as small as possible: add a guard clause, gate on a loading state, reorder an await, wrap in a conditional, etc. Do not refactor surrounding code or expand scope beyond the finding.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
CodeAnt AI finished reviewing your PR. |
❌ Object Smoke Tests FailedTest Results❌ Some smoke tests failed Please fix the failing tests before checking coverage. |
1ce2e69 to
f7951ec
Compare
User description
CodeAnt-AI Description
Add PTY terminal support for devboxes
What Changed
ptysection for terminal work alongside command, file, and network actions.Impact
✅ Terminal access from devboxes✅ Fewer steps to run interactive commands✅ Clearer PTY session control💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.