feat(cloud-task): Durable run streaming through agent-proxy#2519
feat(cloud-task): Durable run streaming through agent-proxy#2519charlesvien wants to merge 1 commit into
Conversation
What T-Rex did
ArtifactsFocused proxy stream edge-case test output
URL parsing proof for unencoded proxy run IDs
Generated focused proxy stream edge-case tests
Vitest output: 1 failed (URL encoding bug), 2 passed, 1 unhandled rejection (bootstrap race)
Generated proxy-stream-edge-cases TypeScript tests
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
apps/code/src/main/services/cloud-task/service.ts:654-658
**Encode proxy run IDs**
The proxy stream path inserts `watcher.runId` directly into the URL. If a run ID contains URL-special characters such as `/`, `?`, or `&`, `new URL()` treats those characters as path or query delimiters and the request goes to the wrong proxy route. The ingest side already encodes `runId`, so the read side should do the same.
```suggestion
const url = new URL(
usingProxy
? `${base}/v1/runs/${encodeURIComponent(watcher.runId)}/stream`
: `${base}/api/projects/${watcher.teamId}/tasks/${watcher.taskId}/runs/${watcher.runId}/stream/`,
);
```
### Issue 2 of 2
apps/code/src/main/services/cloud-task/service.ts:1188-1191
**Preserve bootstrap snapshot**
When `stream-end` arrives while bootstrap is still fetching historical logs, this branch stops and deletes the watcher before the bootstrap path can emit its snapshot. The pending `bootstrapWatcher()` then sees that the watcher is gone and returns, so subscribers never receive the existing run history. The `streamEnded && isBootstrapping` case needs to defer stopping until after bootstrap finishes.
Reviews (1): Last reviewed commit: "Implement agent proxy" | Re-trigger Greptile |
| const url = new URL( | ||
| `${watcher.apiHost}/api/projects/${watcher.teamId}/tasks/${watcher.taskId}/runs/${watcher.runId}/stream/`, | ||
| usingProxy | ||
| ? `${base}/v1/runs/${watcher.runId}/stream` | ||
| : `${base}/api/projects/${watcher.teamId}/tasks/${watcher.taskId}/runs/${watcher.runId}/stream/`, | ||
| ); |
There was a problem hiding this comment.
The proxy stream path inserts watcher.runId directly into the URL. If a run ID contains URL-special characters such as /, ?, or &, new URL() treats those characters as path or query delimiters and the request goes to the wrong proxy route. The ingest side already encodes runId, so the read side should do the same.
| const url = new URL( | |
| `${watcher.apiHost}/api/projects/${watcher.teamId}/tasks/${watcher.taskId}/runs/${watcher.runId}/stream/`, | |
| usingProxy | |
| ? `${base}/v1/runs/${watcher.runId}/stream` | |
| : `${base}/api/projects/${watcher.teamId}/tasks/${watcher.taskId}/runs/${watcher.runId}/stream/`, | |
| ); | |
| const url = new URL( | |
| usingProxy | |
| ? `${base}/v1/runs/${encodeURIComponent(watcher.runId)}/stream` | |
| : `${base}/api/projects/${watcher.teamId}/tasks/${watcher.taskId}/runs/${watcher.runId}/stream/`, | |
| ); |
Artifacts
Malformed proxy stream URL proof
- Keeps the command output available without making the summary code-heavy.
Focused proxy stream edge-case test output
- Keeps the command output available without making the summary code-heavy.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/main/services/cloud-task/service.ts
Line: 654-658
Comment:
**Encode proxy run IDs**
The proxy stream path inserts `watcher.runId` directly into the URL. If a run ID contains URL-special characters such as `/`, `?`, or `&`, `new URL()` treats those characters as path or query delimiters and the request goes to the wrong proxy route. The ingest side already encodes `runId`, so the read side should do the same.
```suggestion
const url = new URL(
usingProxy
? `${base}/v1/runs/${encodeURIComponent(watcher.runId)}/stream`
: `${base}/api/projects/${watcher.teamId}/tasks/${watcher.taskId}/runs/${watcher.runId}/stream/`,
);
```
<details><summary><strong>Artifacts</strong></summary><br />
**[Malformed proxy stream URL proof](https://app.greptile.com/trex/artifacts/ddb62d93-805a-4883-af2f-93bfa316a395)**
- Keeps the command output available without making the summary code-heavy.
**[Focused proxy stream edge-case test output](https://app.greptile.com/trex/artifacts/195501f6-4213-4d1e-9e91-701e80403f1a)**
- Keeps the command output available without making the summary code-heavy.
</details>
<sub><a href="https://www.greptile.com/trex"><img alt="T-Rex" src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg" height="14" align="absmiddle"></a> Ran code and verified through T-Rex</sub>
How can I resolve this? If you propose a fix, please make it concise.| if (watcher.streamEnded) { | ||
| this.emitStatusUpdate(watcher); | ||
| this.stopWatcher(key); | ||
| return; |
There was a problem hiding this comment.
When stream-end arrives while bootstrap is still fetching historical logs, this branch stops and deletes the watcher before the bootstrap path can emit its snapshot. The pending bootstrapWatcher() then sees that the watcher is gone and returns, so subscribers never receive the existing run history. The streamEnded && isBootstrapping case needs to defer stopping until after bootstrap finishes.
Artifacts
Focused stream-end during bootstrap repro output
- Keeps the command output available without making the summary code-heavy.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/main/services/cloud-task/service.ts
Line: 1188-1191
Comment:
**Preserve bootstrap snapshot**
When `stream-end` arrives while bootstrap is still fetching historical logs, this branch stops and deletes the watcher before the bootstrap path can emit its snapshot. The pending `bootstrapWatcher()` then sees that the watcher is gone and returns, so subscribers never receive the existing run history. The `streamEnded && isBootstrapping` case needs to defer stopping until after bootstrap finishes.
<details><summary><strong>Artifacts</strong></summary><br />
**[Focused stream-end during bootstrap repro output](https://app.greptile.com/trex/artifacts/195501f6-4213-4d1e-9e91-701e80403f1a)**
- Keeps the command output available without making the summary code-heavy.
</details>
<sub><a href="https://www.greptile.com/trex"><img alt="T-Rex" src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg" height="14" align="absmiddle"></a> Ran code and verified through T-Rex</sub>
How can I resolve this? If you propose a fix, please make it concise.
Problem
Moves event streaming to agent-proxy
Changes
How did you test this?
Manually
Automatic notifications