You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When a caller uses the agentResultSchema / result / waitForResult APIs from @agent-relay/sdk and spawns the broker via AgentRelayClient.spawn() (i.e. not via the agent-relay CLI), the spawned agent has no way to submit a structured result. The promise from waitForResult never resolves, no agent_result event ever fires, and the caller sees a silent hang (or a fallback-path failure on their end).
This was introduced with PR #932 ("Add structured agent result callbacks", commit 839d0cd). The feature works end-to-end only when the broker is launched via the agent-relay CLI, because that's the only code path that sets up the patched relaycast MCP.
Where the wiring breaks
The structured-result feature has four moving parts:
SDK → broker spawn input. Caller passes agentResultSchema on SpawnPtyInput. SDK forwards it as agent_result_schema to the broker.
Broker → MCP env injection. The broker constructs an AgentResultMcpConfig and injects AGENT_RELAY_RESULT_URL, AGENT_RELAY_RESULT_TOKEN, AGENT_RELAY_RESULT_SCHEMA into the relaycast MCP's environment (not into the agent's environment directly). See crates/broker/src/types.rs:230-239:
MCP → submit_result tool. A patched relaycast MCP reads those env vars and registers a `submit_result` MCP tool that POSTs to /api/agent-result. This patched MCP lives in src/cli/relaycast-mcp.ts:120-210 (added in commit 839d0cd):
Broker → agent_result event. Broker accepts the POST and emits an agent_result BrokerEvent. SDK turns it into the agentResult event / resolves waitForResult.
Steps 2 and 4 work correctly. Step 3 is where things break for SDK consumers.
The actual bug: which MCP gets launched
The broker decides which MCP binary to launch via crates/broker/src/snippets.rs:240-266:
ifletOk(custom_cmd) = std::env::var(\"RELAYCAST_MCP_COMMAND\"){// use the custom command (parsed into argv)}else{
server.insert(\"command\".into(),Value::String(\"npx\".into()));
server.insert(\"args\".into(),Value::Array(vec![Value::String(\"-y\".into()),Value::String(RELAYCAST_MCP_PACKAGE.into()),// \"@relaycast/mcp\"]));}
The agent-relay CLI sets RELAYCAST_MCP_COMMAND before spawning the broker — see src/cli/lib/broker-lifecycle.ts:454-463 and src/cli/lib/relaycast-mcp-command.ts. It points at the patched relaycast-mcp.js bundled inside agent-relay/dist/src/cli/.
AgentRelayClient.spawn() does not set RELAYCAST_MCP_COMMAND. Verified in the installed SDK:
$ grep -rn \"RELAYCAST_MCP_COMMAND\" node_modules/@agent-relay/sdk/dist/
# (no matches)
So an SDK-spawned broker falls through to npx -y @relaycast/mcp, which resolves to the unpatched@relaycast/mcp@1.2.0 from the npm registry. Confirmed that package has zero references to submit_result or AGENT_RELAY_RESULT_*:
$ npm pack @relaycast/mcp@1.2.0 && tar xzf relaycast-mcp-1.2.0.tgz
$ grep -rn 'submit_result\\|AGENT_RELAY_RESULT' package/
# (no matches)
$ npm pack agent-relay@7.1.0 && tar xzf agent-relay-7.1.0.tgz
$ grep -c 'submit_result\\|AGENT_RELAY_RESULT' package/dist/src/cli/relaycast-mcp.js
5
So the agent gets the unpatched relaycast MCP, which has no submit_result tool. The agent has no way to call back. agent_result never fires. waitForResult hangs forever.
Reproduction
import{AgentRelayClient}from'@agent-relay/sdk';constclient=awaitAgentRelayClient.spawn({cwd: '/some/dir'});constspawned=awaitclient.spawnPty({name: 'commit-draft',cli: 'codex',task: 'Output a JSON object {\"title\": string, \"body\": string} describing this diff: ...',agentResultSchema: {type: 'object',properties: {title: {type: 'string'},body: {type: 'string'}},required: ['title'],},});// Wait for agent_result event — hangs forever, even after the agent has output a perfectly valid JSON to stdout.client.onEvent((event)=>{if(event.kind==='agent_result'&&event.name===spawned.name){console.log('got result',event.data);}});
Using the AgentRelay facade and agent.waitForResult() has the same failure mode — the underlying broker event never fires, so the promise never resolves.
Expected vs Actual
Expected: SDK consumers calling agentResultSchema / waitForResult get the same end-to-end behavior they'd get if they launched the broker via agent-relay up.
Actual: Silent hang. No agent_result event, no error, no warning logged. The unpatched MCP doesn't even know it's expected to register a submit_result tool, so there's no diagnostic on that side either.
Fix options (in order of preference)
SDK auto-sets RELAYCAST_MCP_COMMAND in AgentRelayClient.spawn(). This requires the SDK to either bundle the patched relaycast-mcp.js itself, or take a dep on agent-relay (or a new sub-package) that provides the script. This is the right fix — the SDK is the public surface for the structured-result API, and it currently silently ships a broken feature.
Publish the patched MCP as @relaycast/mcp@1.3 so the unpatched fallback path also works. This fixes downstream users who never set RELAYCAST_MCP_COMMAND, including non-SDK consumers.
Extract the patched MCP into a published @agent-relay/relaycast-mcp package (the client identifier already used in src/cli/relaycast-mcp.ts:358 hints this was the original plan) and have the SDK reference it via RELAYCAST_MCP_COMMAND=\"npx -y @agent-relay/relaycast-mcp\".
Minimum viable: fail loud. Even without fixing the underlying issue, the SDK should detect that the result feature was requested + no RELAYCAST_MCP_COMMAND was set + the default @relaycast/mcp is the unpatched version, and either warn or throw. Today the failure is silent, which is the worst-of-both for callers.
Repro environment
@agent-relay/sdk@7.1.0
@relaycast/mcp@1.2.0 (resolved by npx -y @relaycast/mcp inside the broker)
Broker binary bundled with @agent-relay/sdk@7.1.0 (agent-relay-broker-* v7.1.0)
Be careful with the agent-name path: the patched MCP reads agentName from PatchedMcpServerOptions, not from env. If the SDK invokes the MCP without a --name arg, the submit_result POST will be missing the agent field and the broker may drop it. Check the wiring in both the CLI and any SDK-side launcher.
Worth adding an end-to-end test that uses AgentRelayClient.spawn() + agentResultSchema + waitForResult against a real codex/claude harness, since the existing test coverage for Add structured agent result callbacks #932 (per the merge stat) is mostly unit-level on the SDK side.
Summary
When a caller uses the
agentResultSchema/result/waitForResultAPIs from@agent-relay/sdkand spawns the broker viaAgentRelayClient.spawn()(i.e. not via theagent-relayCLI), the spawned agent has no way to submit a structured result. The promise fromwaitForResultnever resolves, noagent_resultevent ever fires, and the caller sees a silent hang (or a fallback-path failure on their end).This was introduced with PR #932 ("Add structured agent result callbacks", commit 839d0cd). The feature works end-to-end only when the broker is launched via the
agent-relayCLI, because that's the only code path that sets up the patched relaycast MCP.Where the wiring breaks
The structured-result feature has four moving parts:
SDK → broker spawn input. Caller passes
agentResultSchemaonSpawnPtyInput. SDK forwards it asagent_result_schemato the broker.Broker → MCP env injection. The broker constructs an
AgentResultMcpConfigand injectsAGENT_RELAY_RESULT_URL,AGENT_RELAY_RESULT_TOKEN,AGENT_RELAY_RESULT_SCHEMAinto the relaycast MCP's environment (not into the agent's environment directly). Seecrates/broker/src/types.rs:230-239:MCP → submit_result tool. A patched relaycast MCP reads those env vars and registers a `submit_result` MCP tool that POSTs to
/api/agent-result. This patched MCP lives insrc/cli/relaycast-mcp.ts:120-210(added in commit 839d0cd):Broker → agent_result event. Broker accepts the POST and emits an
agent_resultBrokerEvent. SDK turns it into theagentResultevent / resolveswaitForResult.Steps 2 and 4 work correctly. Step 3 is where things break for SDK consumers.
The actual bug: which MCP gets launched
The broker decides which MCP binary to launch via
crates/broker/src/snippets.rs:240-266:The
agent-relayCLI setsRELAYCAST_MCP_COMMANDbefore spawning the broker — seesrc/cli/lib/broker-lifecycle.ts:454-463andsrc/cli/lib/relaycast-mcp-command.ts. It points at the patchedrelaycast-mcp.jsbundled insideagent-relay/dist/src/cli/.AgentRelayClient.spawn()does not setRELAYCAST_MCP_COMMAND. Verified in the installed SDK:So an SDK-spawned broker falls through to
npx -y @relaycast/mcp, which resolves to the unpatched@relaycast/mcp@1.2.0from the npm registry. Confirmed that package has zero references tosubmit_resultorAGENT_RELAY_RESULT_*:So the agent gets the unpatched relaycast MCP, which has no
submit_resulttool. The agent has no way to call back.agent_resultnever fires.waitForResulthangs forever.Reproduction
Using the
AgentRelayfacade andagent.waitForResult()has the same failure mode — the underlying broker event never fires, so the promise never resolves.Expected vs Actual
Expected: SDK consumers calling
agentResultSchema/waitForResultget the same end-to-end behavior they'd get if they launched the broker viaagent-relay up.Actual: Silent hang. No
agent_resultevent, no error, no warning logged. The unpatched MCP doesn't even know it's expected to register asubmit_resulttool, so there's no diagnostic on that side either.Fix options (in order of preference)
SDK auto-sets
RELAYCAST_MCP_COMMANDinAgentRelayClient.spawn(). This requires the SDK to either bundle the patchedrelaycast-mcp.jsitself, or take a dep onagent-relay(or a new sub-package) that provides the script. This is the right fix — the SDK is the public surface for the structured-result API, and it currently silently ships a broken feature.Publish the patched MCP as
@relaycast/mcp@1.3so the unpatched fallback path also works. This fixes downstream users who never setRELAYCAST_MCP_COMMAND, including non-SDK consumers.Extract the patched MCP into a published
@agent-relay/relaycast-mcppackage (theclientidentifier already used insrc/cli/relaycast-mcp.ts:358hints this was the original plan) and have the SDK reference it viaRELAYCAST_MCP_COMMAND=\"npx -y @agent-relay/relaycast-mcp\".Minimum viable: fail loud. Even without fixing the underlying issue, the SDK should detect that the result feature was requested + no
RELAYCAST_MCP_COMMANDwas set + the default@relaycast/mcpis the unpatched version, and either warn or throw. Today the failure is silent, which is the worst-of-both for callers.Repro environment
@agent-relay/sdk@7.1.0@relaycast/mcp@1.2.0(resolved bynpx -y @relaycast/mcpinside the broker)@agent-relay/sdk@7.1.0(agent-relay-broker-*v7.1.0)Notes for the fixer
src/cli/relaycast-mcp.tsof this repo; PR Add structured agent result callbacks #932 (commits 839d0cd → d76b0f4) added it.agentNamefromPatchedMcpServerOptions, not from env. If the SDK invokes the MCP without a--namearg, thesubmit_resultPOST will be missing theagentfield and the broker may drop it. Check the wiring in both the CLI and any SDK-side launcher.AgentRelayClient.spawn()+agentResultSchema+waitForResultagainst a real codex/claude harness, since the existing test coverage for Add structured agent result callbacks #932 (per the merge stat) is mostly unit-level on the SDK side.