Visitar URL original
fix: stop get_network_request waiting forever for an unread no-store body by kevin9327 · Pull Request #3216 · ChromeDevTools/chrome-devtools-mcp · GitHub
Skip to content

fix: stop get_network_request waiting forever for an unread no-store body - #3216

Merged
OrKoN merged 3 commits into
ChromeDevTools:mainfrom
kevin9327:fix/network-body-unread-no-store
Oct 20, 2026
Merged

OrKoN merged 3 commits into
ChromeDevTools:mainfrom
kevin9327:fix/network-body-unread-no-store

Conversation

@kevin9327

Copy link
Copy Markdown
Contributor

Fixes #3213.

get_network_request reads the response body with Puppeteer's HTTPResponse.buffer(). That waits for the response to finish loading, with no timeout of its own, before it sends Network.getResponseBody. Chrome never finishes loading a Cache-Control: no-store response that the page doesn't read (e.g. if (!res.ok) throw ... on a 500), so the call never returns. It runs under the tool mutex, which blocks every later tool call on the server.

I reproduced this locally with Puppeteer and headless Chrome. For a POST that returns 500 with no-store and that the page doesn't read, buffer() was still pending after 8 s at body sizes of 100 B, 100 KB and 5 MB. Without no-store, or when the page reads the body, it resolves in ~3 ms.

The fix: NetworkFormatter now races the body read against RESPONSE_BODY_TIMEOUT_MS (5 s; tests can lower it with a new responseBodyTimeout option). On timeout, the response body shows as <Response body not available: the response did not finish loading within 5000 ms>, and the status, headers and the rest of the output are returned as usual. With responseFilePath, nothing is saved and the same message is shown instead of the generic "not available anymore". Responses that finish loading behave exactly as before.

Tests:

  • McpResponse: a real page POSTs to a no-store 500 route and ignores the body. The attached request now returns its status and the timeout message instead of hanging.
  • NetworkFormatter: a response whose buffer() never settles gets the message both inline and with responseFilePath, and saveFile is not called.
  • npm run build, eslint . and prettier --check are clean. McpResponse, NetworkFormatter, tools/network and McpContext tests: 186/186 pass.

One trade-off: a large body that takes longer than 5 s to finish downloading also gets the message now instead of a long wait. I kept the cap short because the tool mutex is held for the whole wait. I'm happy to change the value.

AI-assisted: I used an AI assistant to help write this change. I reviewed the code and tests and ran them locally.

@OrKoN
OrKoN self-requested a review October 17, 2026 05:08

@OrKoN OrKoN left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check the following:

  1. In src/formatters/NetworkFormatter.ts, remove the early return in the responseFilePath catch block and set this.#responseBody to the timeout message or responseBodyNotAvailableMessage in the catch, then keep only if (buffer) {...}. The return exits the whole init method and only works because the response body block is the last step.

  2. In tests/McpResponse.test.ts, the new test waits the full RESPONSE_BODY_TIMEOUT_MS (5 s) on every run; let McpResponse pass a shorter responseBodyTimeout in tests, or drop the real-browser test and keep the two mock tests. The suite already runs long, and the timeout logic is covered by the NetworkFormatter tests.

Nit (non-blocking): the <Response body not available: ...> text is both the Error.message and the user-facing output; keep the error message plain (Response body timed out after N ms) and format the <...> placeholder where it is rendered, consistent with responseBodyNotAvailableMessage.

@kevin9327

Copy link
Copy Markdown
Contributor Author

@OrKoN thanks, all three addressed in fbd26d7:

  1. The responseFilePath catch now sets this.#responseBody to the timeout placeholder (or responseBodyNotAvailableMessage for any other failure), and the early return is gone. Only if (buffer) {...} remains after it.
  2. I dropped the real-browser test from tests/McpResponse.test.ts, so nothing waits the full 5 s any more. The two NetworkFormatter mock tests (10 ms timeout) still cover the inline and file-path cases.
  3. Nit: ResponseBodyTimeoutError now carries a plain Response body timed out after N ms message, and the <Response body not available: ...> placeholder is built where the body is rendered.

tsc and eslint are clean, and the NetworkFormatter tests pass locally.

@OrKoN
OrKoN self-requested a review October 19, 2026 12:18

@OrKoN OrKoN left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the following:

  1. In tests/formatters/NetworkFormatter.test.ts, test "does not save a response body that never finishes loading": replace the saved flag with const saveFile = sinon.stub().resolves({filename: resPath}) and assert sinon.assert.notCalled(saveFile), and assert json.responseBody with assert.strictEqual against the full message instead of assert.match(json.responseBody ?? '', ...). AGENTS.md requires sinon.assert for stub calls and exact assertions; the current saveFile stub also names the data argument filename.

  2. In the same file, test "stops waiting for a response body that never finishes loading": remove the as {responseBody: string} cast and read formatter.toJSONDetailed().responseBody directly. toJSONDetailed() already returns NetworkRequestDetailed with responseBody?: string, and AGENTS.md forbids as casts.

Nit (non-blocking): tests/McpResponse.test.ts in the PR diff shows unrelated hunks (removed afterEach(sinon.restore), a new CORS preflight test) because the file was copied from a newer main instead of rebasing; the file is byte-identical to main, so the squash merge is a no-op, but a rebase makes the PR diff show only the fix.

Keep the timeout error message plain and format the placeholder where
the body is rendered. Drop the real-browser test, which waited the full
5 s body timeout; the NetworkFormatter mock tests cover the timeout.
@kevin9327

kevin9327 commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor Author

@OrKoN thanks, both done in 755d580:

  1. "does not save a response body that never finishes loading" now uses const saveFile = sinon.stub().resolves({filename: resPath}), checks it with sinon.assert.notCalled(saveFile), and compares json.responseBody to the full message with assert.strictEqual.
  2. "stops waiting for a response body that never finishes loading" reads formatter.toJSONDetailed().responseBody directly, with no cast (the other test lost its cast too).

I also rebased onto current main, so the PR diff is now only NetworkFormatter.ts and its test file. The tests/McpResponse.test.ts noise is gone. The NetworkFormatter and McpResponse tests pass locally.

@kevin9327
kevin9327 force-pushed the fix/network-body-unread-no-store branch from fbd26d7 to 755d580 Compare October 20, 2026 00:22
@OrKoN
OrKoN enabled auto-merge October 20, 2026 05:55
@OrKoN
OrKoN added this pull request to the merge queue Oct 20, 2026
Merged via the queue into ChromeDevTools:main with commit c556be1 Oct 20, 2026
14 checks passed
github-merge-queue Bot pushed a commit that referenced this pull request Oct 22, 2026
🤖 I have created a release *beep* *boop*
---


##
[1.12.0](chrome-devtools-mcp-v1.11.0...chrome-devtools-mcp-v1.12.0)
(2026-10-22)


### 🎉 Features

* add --maxNavigationSaved flag to configure retained navigation history
([#3018](#3018))
([a580d13](a580d13))
* add Kimi Code plugin manifest
([#3029](#3029))
([cd00b64](cd00b64))
* Analyze heap snapshots without a browser connection
([#3156](#3156))
([78c8320](78c8320))
* **config:** add --watchConfig to reload config file changes
([#3248](#3248))
([b48561f](b48561f))
* **config:** introduce layered ServerConfig for live reload
([#3203](#3203))
([37f1fda](37f1fda))
* **config:** share ReadonlyServerConfig with McpContext and McpPage
([#3204](#3204))
([0813bf3](0813bf3))
* **config:** support reloading blockedUrlPattern and allowedUrlPattern
([#3268](#3268))
([cc850f5](cc850f5))
* **context:** release disabled feature state on config reload
([#3049](#3049))
([c0140d1](c0140d1))
* Paginate by default in heap snapshot tools
([#3201](#3201))
([fb8375b](fb8375b))
* Paginate detailed diff in compare_heapsnapshots
([#3195](#3195))
([4a9dd53](4a9dd53))
* run independent tool calls in parallel with a TaskRunner
([#3243](#3243))
([8a8d877](8a8d877))
* run page-scoped tools on different pages in parallel
([#3252](#3252))
([f8f18ac](f8f18ac))
* **server:** add McpServer.applyConfig() to reload configuration
([#3051](#3051))
([f874aeb](f874aeb))
* **slim:** support filepath in slim mode screenshot
([#3296](#3296))
([d399c4a](d399c4a))
* Sort objects for class by self size in compare_heapsnapshots
([#3205](#3205))
([f8a18c6](f8a18c6))
* **tools:** support updating ToolHandler definitions in place
([#3050](#3050))
([8064138](8064138))
* **wait_for:** support opt-in visible text matching
([#3212](#3212))
([eef92da](eef92da))


### 🛠️ Fixes

* allow paths below a filesystem-root workspace root
([#3277](#3277))
([296ccef](296ccef))
* block evaluate_script while a dialog is open
([#3196](#3196))
([545bcbb](545bcbb))
* bound take_snapshot wait and clear the geolocation override
([#3265](#3265))
([6282f54](6282f54))
* **browser:** redact WebSocket headers from connection logs
([#2901](#2901))
([3a2f91e](3a2f91e))
* capture thrown transform errors as Zod validation issues
([#3242](#3242))
([0f95c21](0f95c21))
* clear handled dialog in McpPage.getDialog()
([#3241](#3241))
([2ab93a3](2ab93a3)),
closes
[#2958](#2958)
* **cli:** disallow --slim in CLI start options
([#2987](#2987))
([#3113](#3113))
([ae3f61c](ae3f61c))
* **cli:** do not forward tool args to start
([#3114](#3114))
([fef1598](fef1598))
* **cli:** serialize object daemon arguments
([#2797](#2797))
([f73a440](f73a440))
* close the tab opened by a failed new_page
([#2928](#2928))
([c174677](c174677))
* **config:** accept native JSON objects for wsHeaders in config files
([#3262](#3262))
([371e6c2](371e6c2))
* **console:** describe Map, Set, function, DOM node, symbol and cyclic
arguments
([#3199](#3199))
([968e4e5](968e4e5))
* **console:** include the reason of unhandled promise rejections
([#3148](#3148))
([f7cdf73](f7cdf73))
* defer focus emulation until a page is explicitly targeted
([#3230](#3230))
([35abe35](35abe35))
* disable Puppeteer signal handlers for launched browsers
([#3238](#3238))
([3afec73](3afec73)),
closes
[#3154](#3154)
* discover third-party developer tools before tool lookup
([#3208](#3208))
([2777fa0](2777fa0)),
closes
[#2973](#2973)
* discover third-party developer tools in page tools
([#3207](#3207))
([cf33529](cf33529)),
closes
[#2980](#2980)
* drop InlineTextBox nodes from text snapshots
([#3202](#3202))
([f8c775a](f8c775a))
* **emulate:** keep previous viewport when setViewport fails
([#3150](#3150))
([19d883c](19d883c))
* exclude source maps from the npm package
([#3295](#3295))
([d575da4](d575da4))
* keep surrogate pairs and emoji whole when truncating titles, names and
bodies
([#3215](#3215))
([058adcf](058adcf))
* let resourceTypes match CORS preflights and prefetches
([#3217](#3217))
([e342c6e](e342c6e))
* **lighthouse:** explain unsupported URL schemes in navigation mode
([#3144](#3144))
([c6ad84a](c6ad84a))
* **lighthouse:** fail the audit when Lighthouse reports a runtime error
([#3188](#3188))
([53ea78d](53ea78d))
* **lighthouse:** restore the emulated viewport after a navigation audit
([#3189](#3189))
([c777ece](c777ece))
* **network:** list only earlier hops in a request's redirect chain
([#3198](#3198))
([4675940](4675940))
* **network:** remove unsupported websocket from list_network_requests
filter
([#2961](#2961))
([#3282](#3282))
([8b119d9](8b119d9))
* **network:** reset page network emulation before applying URL
restrictions
([#3312](#3312))
([f25f3df](f25f3df))
* **pages:** reject devtools:// URLs in navigate_page and new_page
([#2935](#2935))
([#3128](#3128))
([c94c03d](c94c03d))
* **pages:** report the actual page size after resize_page
([#2998](#2998))
([#3132](#3132))
([f003056](f003056))
* **pages:** revert reporting the page size after resize_page
([#3308](#3308))
([186ea66](186ea66)),
closes
[#3306](#3306)
* **pagination:** return all items when pageSize is omitted and pageIdx
is 0
([#2996](#2996))
([#3134](#3134))
([3ad9115](3ad9115))
* **performance:** populate structuredContent for traceInsight in
performance_analyze_insight
([#2981](#2981))
([#3115](#3115))
([c2c6cc4](c2c6cc4))
* preserve unread page fallback across snapshots and deduplicate page
disposal
([#3161](#3161))
([8604365](8604365))
* print the no-roots temp-directory warning under default flags
([#2927](#2927))
([0dd8ed9](0dd8ed9))
* reject empty, extra, and unknown parts in geolocation and viewport
transforms
([#3097](#3097))
([#3120](#3120))
([3ea31fb](3ea31fb))
* resolve error causes on the session that reported the exception
([#3220](#3220))
([b2d37e8](b2d37e8))
* resolve service worker IDs for console messages
([#2881](#2881))
([3804a9a](3804a9a))
* return message when page exposes no 3p developer tools
([#3197](#3197))
([663f3c3](663f3c3)),
closes
[#3004](#3004)
* **script:** surface non-Error thrown and rejected values in
evaluate_script
([#2951](#2951))
([#3127](#3127))
([b1e6e68](b1e6e68))
* **security:** validate filesystem paths that parse as http/ws URLs
([#3186](#3186))
([91e112d](91e112d))
* **snapshot:** omit uid for accessibility nodes without backendNodeId
([#3187](#3187))
([9054351](9054351))
* stop active screencast on McpPage.dispose()
([#3160](#3160))
([558af81](558af81))
* stop get_network_request waiting forever for an unread no-store body
([#3216](#3216))
([c556be1](c556be1))
* surface nested error causes and autoConnect hint on connection failure
([#3111](#3111))
([d7f1e78](d7f1e78))
* treat 0.0.0.0 and [::] browser endpoints as local
([#3321](#3321))
([7d5f7de](7d5f7de))
* treat IPv4-mapped IPv6 loopback as localhost
([#3255](#3255))
([6cfb34c](6cfb34c))
* truncate data: URLs in accessibility snapshots
([#3292](#3292))
([11e7d47](11e7d47))
* validate type_text submitKey before typing
([#3185](#3185))
([c8615d7](c8615d7))
* **wait_for:** find text in dynamically attached iframes
([#3210](#3210))
([5f84888](5f84888))
* warn when screenshot dimensions exceed 16384px limit
([#2952](#2952))
([#3291](#3291))
([10455b7](10455b7))


### 📄 Documentation

* add a pre-submit checklist to AGENTS.md
([#3236](#3236))
([e96ee6c](e96ee6c))
* clarify URL pattern port and hostname matching
([#3287](#3287))
([4723335](4723335))
* **config:** note that hot-reloaded URL restrictions keep open targets
attached
([#3314](#3314))
([47db2b8](47db2b8))
* correct CLI availability of extension tools
([#2852](#2852))
([a7280ee](a7280ee))
* explain how to install skills, including the CLI skill
([#3274](#3274))
([46eb546](46eb546))
* remove experimental status from CLI and promote it in README
([#3273](#3273))
([0ced16b](0ced16b))
* require a rebase only when the branch has merge conflicts
([#3239](#3239))
([d1143ac](d1143ac))
* **skills:** clarify Lighthouse score scale and contrast workflow
([#3006](#3006))
([#3218](#3218))
([bf8c6d9](bf8c6d9))


### ⚡ Performance

* **cli:** load a small third-party bundle in the chrome-devtools CLI
([#3200](#3200))
([552e1f8](552e1f8))
* load the Lighthouse bundle on first use
([#3169](#3169))
([798f03a](798f03a))


### 🏗️ Refactor

* **config:** isolate scripts and unit tests from home config discovery
([#3256](#3256))
([65e6b97](65e6b97))
* **config:** lock slim and pageIdRouting options when viaCli is set
([#3257](#3257))
([384d9aa](384d9aa))
* **config:** pass ReadonlyServerConfig to BrowserManager and add
OptionGate
([#3267](#3267))
([98302f2](98302f2))
* **config:** simplify watchConfig and start watcher inside connect
([#3259](#3259))
([878c5f6](878c5f6))
* extract errorResult() and routedPageId() in ToolHandler
([#3251](#3251))
([9a6b03a](9a6b03a))
* import types and values from a module in one statement
([#3303](#3303))
([f4eaf1f](f4eaf1f))
* **lighthouse:** move Lighthouse runner methods to McpPage and add
bundle types
([#3283](#3283))
([e1823a2](e1823a2))
* list optional properties last in inline object types
([#3302](#3302))
([dc929c0](dc929c0))
* register all tools upfront across modes
([#3163](#3163))
([58f8d1f](58f8d1f))
* **response:** pass live ReadonlyServerConfig to McpResponse
([#3258](#3258))
([6042490](6042490))
* **tools:** add explicit factory types to tool definitions
([#3284](#3284))
([4d1d7b2](4d1d7b2))
* use #private names instead of the private modifier
([#3301](#3301))
([2e13311](2e13311))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

get_network_request never returns for an unread Cache-Control: no-store response and blocks every other tool

2 participants