Follow-ups on a completed cloud agent are accepted (HTTP 201) and go CREATING → RUNNING → ERROR after a few minutes. GET /v1/agents/{id}/runs/{runId} returns error: null and no result. The initial run on the same agent finished successfully.
Hey, thanks for the detailed report with the run IDs and bcId. That makes it much easier to debug.
It looks like the agent is running on a self-hosted worker pool or machine. The follow-up run reuses the binding to the same worker that handled the initial run and tries to reconnect to it. If that worker goes offline or gets released by the time the follow-up starts, for example the machine shuts down, the worker process stops, or the pool worker gets busy or released after the first run, and there is no other worker online for this repo, the reconnection fails after the reconnect budget. By default that is about 4 minutes, which matches your roughly 4-minute ERROR, and the run ends with status ERROR.
A couple things to check:
Was the self-hosted worker for this repo still online when you sent the follow-up? Make sure it shows as connected in the Cloud Agents dashboard or My Machines, and that the worker process agent worker start did not crash between the initial run and the follow-up.
If the worker was definitely alive, tell me and we can dig deeper.
About error: null, that is a separate known issue on our side. The REST run payload GET /v1/agents/{id}/runs/{runId} currently does not include the failure reason, so polling will not show it. The reason is sent in the run stream. You will get an error event with {code, message} there, and that shows the real cause. I recommend connecting to the stream so you can see the exact error code for these runs.
We are aware of this reconnect to self-hosted worker error class and we are tracking it, but I cannot share an ETA for a fix yet. I will also make sure the missing error reason in the REST response gets surfaced to the team. Let me know the worker status.
Do I need the exact worker that processed the first message to be online? I’m entirely sure that does not work right now because my workers are ephemeral and replaced after some time, but I know that other workers were available when I did the follow up. What can I test to help your team to debug this? I’m using the k8s helm chart for self hosted cursor agents.
Hey, thanks for the details, this is exactly what we needed.
Yep, short answer: right now the follow-up run gets pinned to the same worker that handled the initial run and tries to reconnect to it, instead of picking a new compatible worker from the pool. So in your case (ephemeral workers on a k8s Helm chart that get recreated on a timeout), the original worker is already gone by the time the follow-up starts, and the run ends up in ERROR after the reconnect budget is used up, even if other workers are online at that moment. Having other workers available doesn’t help since the follow-up path won’t pick them up.
This is a known limitation for ephemeral or pool patterns on self-hosted. We’re tracking it, but I can’t share an ETA for a fix yet.
What would help for debugging:
Subscribe to the run stream via the SDK using run.stream() for a fresh failed follow-up, and send the status event where status: "ERROR". The message field should include the reason, I expect something about a disconnected worker or reconnect. If you go without the SDK, it’s best to confirm the exact REST stream path in the OpenAPI spec: https://cursor.com/docs-static/cloud-agents-openapi.yaml
Share your Helm worker config, specifically the idle timeout and how quickly workers get recycled after being idle.
As a workaround for now, you can increase the worker idle timeout so the original worker stays alive long enough to accept the follow-up before it gets replaced. This doesn’t fully fix the root issue, but it should stop the ERRORs in your setup.
About error: null, that’s a separate known gap. The REST run payload GET /v1/agents/{id}/runs/{runId} doesn’t include the reason right now. I’ve flagged it to the team.
Let me know what the stream shows and what your worker timeouts are set to.
Setup: k8s self-hosted pool via the official Helm chart (`WorkerDeployment` CRD + `worker-set-controller-chart` `0.1.0-73528c2`). Workers run `agent worker start --pool --idle-release-timeout 600` with `readyReplicas: 4`. Workspace is `emptyDir`, so pods are fully ephemeral.
Timing: our initial run took ~10 minutes; idle release is 600s. So by the time we send a follow-up, the original worker pod is often already recycled even though other pool workers are connected.
This blocks our intended use of REST `POST /v1/agents/{id}/runs` follow-ups on a self-hosted pool. Follow-ups should be able to re-bind to any compatible worker in the pool, and failed runs should return a useful error on `GET /v1/agents/{id}/runs/{runId}` instead of `error: null`.
Thanks, that confirms it. Your setup matches the root cause exactly.
Since your pods are fully ephemeral emptyDir with --idle-release-timeout 600 and the first run takes about 10 minutes, the original worker is already released and recycled by the time you send the follow-up. And because the follow-up flow pins to that original workerId instead of picking any compatible worker from the pool, the other online workers don’t get used, and the run ERRORs once the reconnect budget is exhausted. So you’re right on both points: the follow-up re-bind behavior and the error: null gap in the REST payload are both real.
Workaround for now, so you’re not blocked:
Increase --idle-release-timeout above your longest expected run duration plus the time before you send the follow-up, so the original worker stays alive long enough to accept it. With about 10 minute runs, 600s is too short. The idle timer starts after the run finishes, so if the follow-up doesn’t arrive within that window, the pod is gone. This won’t fix the pinning behavior, but it should stop the ERRORs in your pool.
To see the real failure reason in the meantime, read it from the run stream. Check the SSE error event with {code, message}. The REST poll won’t show it yet.
Both issues are logged on our side: the follow-up pinning to a dead worker instead of re-binding to the pool, and the missing failure reason on the REST run payload. I can’t share an ETA for either, but I’ll update this thread if anything moves.