Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

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

未关闭
#155 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
3/5
预计耗时
1-2 天
新手友好度
74/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
冷清
技术栈
typescript
领域
api, testing

调研方向

从 AgentStream.getResult() 和 AgentRuntime.run() 开始,然后将它们的行为与非流式 _pollForCompletion() 路径进行比较。使用 Suite 8 的 guardrail 用例重现该问题,并验证流式运行会等待服务器的终止状态,并返回 FAILED 或 COMPLETED,而不是 RUNNING。

由索引模型根据 Issue 内容生成。

描述

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.

主要语言
TypeScript
星标
58
派生
20
平均合并
6 天 5 小时
30 天内合并 PR
3

环境准备

我们还没有检查这个项目的环境配置文件。先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

conductor-oss/javascript-sdk 的其他 Issue

查看 conductor-oss/javascript-sdk 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。