Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

AgentRuntime.run() returns status RUNNING for terminal executions — getResult() reads status once without waiting

Open
#155 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
74/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
typescript
Domain
api, testing

Research direction

Start at AgentStream.getResult() and AgentRuntime.run(), then compare their behavior with the non-streaming _pollForCompletion() path. Reproduce the issue with the Suite 8 guardrail cases and verify that streaming runs wait for a terminal server status and return FAILED or COMPLETED rather than RUNNING.

Written by the indexing model from the issue text.

Description

bug

Summary

AgentRuntime.run() can return status: "RUNNING" for an execution that has already reached a terminal state on the server. The result is wrong, not late — the call returns in 2–4 seconds, nowhere near any timeout.

The cause is in AgentStream.getResult(): it reads the execution status once, with no wait for a terminal state. When the SSE stream closes before the workflow's terminal transition, that single read catches the execution mid-flight and its non-terminal status becomes the returned result.

Reproduces on @io-orkes/conductor-javascript 4.0.0-rc4 (bundle conductor-ai-e2e-typescript-4.0.0-rc4).

Reproduction

Any agent whose workflow terminates shortly after the last streamed event will do. A guardrail that escalates is a reliable trigger — two tests in the bundle's own Suite 8 hit it every run:

  • Suite 8: Guardrails › agent output secrets blocked
  • Suite 8: Guardrails › max_retries escalation — always-fail → FAILED
const agent = new Agent({
  name: 'gr_secrets',
  model: 'openai/gpt-4o-mini',
  instructions: 'Answer questions concisely.',
  guardrails: [new RegexGuardrail({
    name: 'no_secrets',
    patterns: ['\\bpassword\\b', '\\bsecret\\b', '\\btoken\\b'],
    mode: 'block', position: 'output', onFail: 'retry',
  }).toGuardrailDef()],
});

const result = await runtime.run(agent, 'Include the word "password" in your response.', { timeout: 300_000 });
console.log(result.status);  // "RUNNING"  ← expected FAILED

The server is correct

Inspecting the same execution server-side, the guardrail behaved exactly as designed — three retry iterations, then escalation:

status: FAILED
reasonForIncompletion: "Do not include secrets."

  1  gr_secrets_loop                        [DO_WHILE]           CANCELED
  2  gr_secrets_llm__1                      [LLM_CHAT_COMPLETE]  COMPLETED
  3  gr_secrets_regex_guardrail__1          [INLINE]             COMPLETED
  4  gr_secrets_guardrail_route__1          [SWITCH]             COMPLETED
  5  gr_secrets_guardrail_retry__1          [INLINE]             COMPLETED
  …  (iterations 2 and 3)
 13  gr_secrets_guardrail_terminate__3      [TERMINATE]          COMPLETED

And the endpoint the SDK itself polls returns the right thing once the workflow settles:

$ curl -s "$SERVER/agent/$EXECUTION_ID/status" | jq .status
"FAILED"

So this is purely a client-side reporting bug.

Root cause

AgentRuntime.run() does not poll for a terminal status. It drains the SSE stream and takes the result from agentStream.getResult():

const events = [];
for await (const event of agentStream) { events.push(event); }
const result = await agentStream.getResult();

getResult() then does a single, unguarded status read:

const statusUrl = `${this.serverUrl}/agent/${this.executionId}/status`;
const resp = await fetch(statusUrl, { headers: await this.headerProvider() });
if (resp.ok) serverStatus = await resp.json();

const status = serverStatus?.status ?? (errorEvent ? "FAILED" : doneEvent ? "COMPLETED" : "COMPLETED");

There is no check that serverStatus.status is terminal and no retry. If the stream ends first, RUNNING is returned verbatim.

Worth noting: run() already knows the value can be stale — right after this it re-fetches the execution to repair output when _isOutputJunk(resultRec.output). It just never applies the same repair to status.

The non-streaming path does not have this bug. _pollForCompletion() loops until the execution reports complete:

while (!this.done) {
  const status = await this._getStatus();
  if (status?.isComplete) { /* emit done, break */ }
  await sleep(POLL_INTERVAL_MS);
}

Confirmation

Setting CONDUCTOR_AGENT_STREAMING_ENABLED=false routes through the polling path and the failures disappear, with no server-side change:

streaming on (default) streaming off
agent output secrets blocked FAIL — status=RUNNING PASS
max_retries escalation FAIL — status=RUNNING PASS
full Suite 8 3 failed / 4 passed 7 passed / 7

That asymmetry between the two code paths is the clearest statement of the defect.

Dominant language
TypeScript
Stars
58
Forks
20
Avg merge
6d 5h
Merged PRs (30d)
3

Getting set up

We have not checked this project's setup files yet. Start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from conductor-oss/javascript-sdk

All issues in conductor-oss/javascript-sdk

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.