fix(ping): reject when the connection closes before a status response - #1514
Merged
Conversation
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 22, 2026
…wers The 26.1 external tests fail intermittently in CI with a bare `Timeout of 120000ms exceeded` in the `before all` hook, after printing `pinging 26.1 port : N` and never printing `pong`. It is the only version this happens on: over the last 40 failed CI runs the signature appears 4 times, all on 26.1, never on the other 27 versions. The server is not answering status yet. `MinecraftServer.runServer()` only builds its status snapshot after `initServer()` returns, and until that snapshot exists `ServerHandshakePacketListenerImpl` closes status connections without replying. 26.1 widened that window: its `initServer()` logs `Done (...)!` and then calls `saveEverything(false, true, true)`, a full save of every dimension, before returning — 1.21.8's returns right after the log. That is the `Saving chunks for level ...` line only 26.1 prints after `Done`. Since minecraft-wrap calls back on the `Done` line, we ping inside the window: probing every 25ms across a boot, 26.1 hangs up on status until 52ms after `Done` and first answers at 77ms, while 1.21.8 already answers 11ms before it. Retry the ping instead of trusting the first one, with a closeTimeout short enough that a failed attempt is worth retrying. A fixed sleep would be tuned to today's `saveEverything` call and would break the next time a version adds work after `Done`. mc.ping reports this as ETIMEDOUT after its whole closeTimeout, because it never notices the connection closing; PrismarineJS/node-minecraft-protocol#1514 fixes that upstream and makes each failed attempt immediate.
u9g
force-pushed
the
fix-ping-closed-connection
branch
from
August 22, 2026 19:53
c261f43 to
79bae6f
Compare
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 22, 2026
…wers The 26.1 external tests fail intermittently in CI with a bare `Timeout of 120000ms exceeded` in the `before all` hook, after printing `pinging 26.1 port : N` and never printing `pong`. It is the only version this happens on: over the last 40 failed CI runs the signature appears 4 times, all on 26.1, never on the other 27 versions. The server is not answering status yet. `MinecraftServer.runServer()` only builds its status snapshot after `initServer()` returns, and until that snapshot exists `ServerHandshakePacketListenerImpl` closes status connections without replying. 26.1 widened that window: its `initServer()` logs `Done (...)!` and then calls `saveEverything(false, true, true)`, a full save of every dimension, before returning — 1.21.8's returns right after the log. That is the `Saving chunks for level ...` line only 26.1 prints after `Done`. Since minecraft-wrap calls back on the `Done` line, we ping inside the window: probing every 25ms across a boot, 26.1 hangs up on status until 52ms after `Done` and first answers at 77ms, while 1.21.8 already answers 11ms before it. Retry the ping instead of trusting the first one, with a closeTimeout short enough that a failed attempt is worth retrying. A fixed sleep would be tuned to today's `saveEverything` call and would break the next time a version adds work after `Done`. mc.ping reports this as ETIMEDOUT after its whole closeTimeout, because it never notices the connection closing; PrismarineJS/node-minecraft-protocol#1514 fixes that upstream and makes each failed attempt immediate.
ping() settles on exactly three things: server_info, ping, or a client error. A server that accepts the connection and then closes it without answering is invisible to all three, so the promise sits until closeTimeout (120s by default) and then rejects with ETIMEDOUT — a timeout that never actually happened, reported long after the server already told us. Vanilla servers do this on every startup: a status request is closed without a reply until the server has built the status snapshot it serves, which does not happen until startup has finished — slightly after the "Done" line is logged. Probing a server every 25ms across a boot, 26.1 closes status connections until 52ms past that line and first answers at 77ms, while 1.21.8 already answers 11ms before it. So pinging a server as soon as it reports being up lands in that gap often enough to matter. Reject on 'end' when no status has arrived yet. If the status did arrive and the server closes before the pong, the existing noPongTimeout path already resolves with it, so that case is left alone.
u9g
force-pushed
the
fix-ping-closed-connection
branch
from
August 22, 2026 19:58
79bae6f to
66bcc35
Compare
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 22, 2026
…wers The 26.1 external tests fail intermittently in CI with a bare `Timeout of 120000ms exceeded` in the `before all` hook, after printing `pinging 26.1 port : N` and never printing `pong`. It is the only version this happens on: over the last 40 failed CI runs the signature appears 4 times, all on 26.1, never on the other 27 versions. The server is not answering status yet. A server closes status connections without replying until it has built the status snapshot it serves, which does not happen until startup has fully finished — slightly after the "Done" line is logged. 26.1 widened that gap: it saves every dimension after logging "Done" and before finishing startup, which is the `Saving chunks for level ...` line only 26.1 prints after `Done`. Since minecraft-wrap calls back on that line, we ping inside the gap: probing every 25ms across a boot, 26.1 closes status connections until 52ms past `Done` and first answers at 77ms, while 1.21.8 already answers 11ms before it. Retry the ping instead of trusting the first one, with a closeTimeout short enough that a failed attempt is worth retrying. A fixed sleep would be tuned to today's startup sequence and would break the next time a version adds work after `Done`. mc.ping reports this as ETIMEDOUT after its whole closeTimeout, because it never notices the connection closing; PrismarineJS/node-minecraft-protocol#1514 fixes that upstream and makes each failed attempt immediate.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
…wers The 26.1 external tests fail intermittently in CI with a bare `Timeout of 120000ms exceeded` in the `before all` hook, after printing `pinging 26.1 port : N` and never printing `pong`. It is the only version this happens on: over the last 40 failed CI runs the signature appears 4 times, all on 26.1, never on the other 27 versions. The server is not answering status yet. A server closes status connections without replying until it has built the status snapshot it serves, which does not happen until startup has fully finished — slightly after the "Done" line is logged. 26.1 widened that gap: it saves every dimension after logging "Done" and before finishing startup, which is the `Saving chunks for level ...` line only 26.1 prints after `Done`. Since minecraft-wrap calls back on that line, we ping inside the gap: probing every 25ms across a boot, 26.1 closes status connections until 52ms past `Done` and first answers at 77ms, while 1.21.8 already answers 11ms before it. Retry the ping instead of trusting the first one, with a closeTimeout short enough that a failed attempt is worth retrying. A fixed sleep would be tuned to today's startup sequence and would break the next time a version adds work after `Done`. mc.ping reports this as ETIMEDOUT after its whole closeTimeout, because it never notices the connection closing; PrismarineJS/node-minecraft-protocol#1514 fixes that upstream and makes each failed attempt immediate.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
…wers The 26.1 external tests fail intermittently in CI with a bare `Timeout of 120000ms exceeded` in the `before all` hook, after printing `pinging 26.1 port : N` and never printing `pong`. It is the only version this happens on: over the last 40 failed CI runs the signature appears 4 times, all on 26.1, never on the other 27 versions. The server is not answering status yet. A server closes status connections without replying until it has built the status snapshot it serves, which does not happen until startup has fully finished — slightly after the "Done" line is logged. 26.1 widened that gap: it saves every dimension after logging "Done" and before finishing startup, which is the `Saving chunks for level ...` line only 26.1 prints after `Done`. Since minecraft-wrap calls back on that line, we ping inside the gap: probing every 25ms across a boot, 26.1 closes status connections until 52ms past `Done` and first answers at 77ms, while 1.21.8 already answers 11ms before it. Retry the ping instead of trusting the first one, with a closeTimeout short enough that a failed attempt is worth retrying. A fixed sleep would be tuned to today's startup sequence and would break the next time a version adds work after `Done`. mc.ping reports this as ETIMEDOUT after its whole closeTimeout, because it never notices the connection closing; PrismarineJS/node-minecraft-protocol#1514 fixes that upstream and makes each failed attempt immediate.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
…wers The 26.1 external tests fail intermittently in CI with a bare `Timeout of 120000ms exceeded` in the `before all` hook, after printing `pinging 26.1 port : N` and never printing `pong`. It is the only version this happens on: over the last 40 failed CI runs the signature appears 4 times, all on 26.1, never on the other 27 versions. The server is not answering status yet. A server closes status connections without replying until it has built the status snapshot it serves, which does not happen until startup has fully finished — slightly after the "Done" line is logged. 26.1 widened that gap: it saves every dimension after logging "Done" and before finishing startup, which is the `Saving chunks for level ...` line only 26.1 prints after `Done`. Since minecraft-wrap calls back on that line, we ping inside the gap: probing every 25ms across a boot, 26.1 closes status connections until 52ms past `Done` and first answers at 77ms, while 1.21.8 already answers 11ms before it. Retry the ping instead of trusting the first one, with a closeTimeout short enough that a failed attempt is worth retrying. A fixed sleep would be tuned to today's startup sequence and would break the next time a version adds work after `Done`. mc.ping reports this as ETIMEDOUT after its whole closeTimeout, because it never notices the connection closing; PrismarineJS/node-minecraft-protocol#1514 fixes that upstream and makes each failed attempt immediate.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
…wers The 26.1 external tests fail intermittently in CI with a bare `Timeout of 120000ms exceeded` in the `before all` hook, after printing `pinging 26.1 port : N` and never printing `pong`. It is the only version this happens on: over the last 40 failed CI runs the signature appears 4 times, all on 26.1, never on the other 27 versions. The server is not answering status yet. A server closes status connections without replying until it has built the status snapshot it serves, which does not happen until startup has fully finished — slightly after the "Done" line is logged. 26.1 widened that gap: it saves every dimension after logging "Done" and before finishing startup, which is the `Saving chunks for level ...` line only 26.1 prints after `Done`. Since minecraft-wrap calls back on that line, we ping inside the gap: probing every 25ms across a boot, 26.1 closes status connections until 52ms past `Done` and first answers at 77ms, while 1.21.8 already answers 11ms before it. Retry the ping instead of trusting the first one, with a closeTimeout short enough that a failed attempt is worth retrying. A fixed sleep would be tuned to today's startup sequence and would break the next time a version adds work after `Done`. mc.ping reports this as ETIMEDOUT after its whole closeTimeout, because it never notices the connection closing; PrismarineJS/node-minecraft-protocol#1514 fixes that upstream and makes each failed attempt immediate.
rom1504
pushed a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
…wers (#3976) The 26.1 external tests fail intermittently in CI with a bare `Timeout of 120000ms exceeded` in the `before all` hook, after printing `pinging 26.1 port : N` and never printing `pong`. It is the only version this happens on: over the last 40 failed CI runs the signature appears 4 times, all on 26.1, never on the other 27 versions. The server is not answering status yet. A server closes status connections without replying until it has built the status snapshot it serves, which does not happen until startup has fully finished — slightly after the "Done" line is logged. 26.1 widened that gap: it saves every dimension after logging "Done" and before finishing startup, which is the `Saving chunks for level ...` line only 26.1 prints after `Done`. Since minecraft-wrap calls back on that line, we ping inside the gap: probing every 25ms across a boot, 26.1 closes status connections until 52ms past `Done` and first answers at 77ms, while 1.21.8 already answers 11ms before it. Retry the ping instead of trusting the first one, with a closeTimeout short enough that a failed attempt is worth retrying. A fixed sleep would be tuned to today's startup sequence and would break the next time a version adds work after `Done`. mc.ping reports this as ETIMEDOUT after its whole closeTimeout, because it never notices the connection closing; PrismarineJS/node-minecraft-protocol#1514 fixes that upstream and makes each failed attempt immediate.
Member
Author
|
/makerelease |
Merged
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
…g until the server answers (#4010) * ci: install minecraft-wrap from the concurrent-download fix commit minecraft-wrap 1.9.0 writes the version manifest to a path shared by the four per-version test processes and reads it back, so they tear each other's writes and fail in the before-all hook with JSON parse errors. Install PrismarineJS/node-minecraft-wrap#111 until it is released. * ci: also install minecraft-protocol from the ping close fix commit The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released. * fix(test): retry the external test's status ping until the server answers The 26.1 external tests fail intermittently in CI with a bare `Timeout of 120000ms exceeded` in the `before all` hook, after printing `pinging 26.1 port : N` and never printing `pong`. It is the only version this happens on: over the last 40 failed CI runs the signature appears 4 times, all on 26.1, never on the other 27 versions. The server is not answering status yet. A server closes status connections without replying until it has built the status snapshot it serves, which does not happen until startup has fully finished — slightly after the "Done" line is logged. 26.1 widened that gap: it saves every dimension after logging "Done" and before finishing startup, which is the `Saving chunks for level ...` line only 26.1 prints after `Done`. Since minecraft-wrap calls back on that line, we ping inside the gap: probing every 25ms across a boot, 26.1 closes status connections until 52ms past `Done` and first answers at 77ms, while 1.21.8 already answers 11ms before it. Retry the ping instead of trusting the first one, with a closeTimeout short enough that a failed attempt is worth retrying. A fixed sleep would be tuned to today's startup sequence and would break the next time a version adds work after `Done`. mc.ping reports this as ETIMEDOUT after its whole closeTimeout, because it never notices the connection closing; PrismarineJS/node-minecraft-protocol#1514 fixes that upstream and makes each failed attempt immediate. * ci: drop the unreleased-fixes install step minecraft-wrap 1.10.0 and minecraft-protocol 1.68.0 ship these fixes, so the plain npm install already picks them up via ^1.3.0 and ^1.67.0.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
…vent (#4014) * ci: install minecraft-wrap from the concurrent-download fix commit minecraft-wrap 1.9.0 writes the version manifest to a path shared by the four per-version test processes and reads it back, so they tear each other's writes and fail in the before-all hook with JSON parse errors. Install PrismarineJS/node-minecraft-wrap#111 until it is released. * ci: also install minecraft-protocol from the ping close fix commit The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released. * test(consume): eat one bread instead of four and wait on the health event The eat loop asserted the same usingHeldItem transitions on every iteration, so refilling to 20 food re-ran an identical code path three more times at ~1.7s per bread (vanilla's fixed 32-tick eat animation). Drain also polled on a fixed 500ms sleep and ground hunger all the way to zero when consuming only requires food below 20. Now the drain waits on the health event and stops at the first drop, and a single consume covers the same asserts: 8.9s -> 2.4s on 1.8.8, ~9s -> 3.3s on 26.1. * ci: drop the unreleased-fixes install step minecraft-wrap 1.10.0 and minecraft-protocol 1.68.0 ship these fixes, so the plain npm install already picks them up via ^1.3.0 and ^1.67.0.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
* ci: install minecraft-wrap from the concurrent-download fix commit minecraft-wrap 1.9.0 writes the version manifest to a path shared by the four per-version test processes and reads it back, so they tear each other's writes and fail in the before-all hook with JSON parse errors. Install PrismarineJS/node-minecraft-wrap#111 until it is released. * ci: also install minecraft-protocol from the ping close fix commit The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released. * Set test server view-distance to 8 to speed up chunk streaming The default view-distance of 10 makes the server generate and stream a 21x21 chunk square on every dimension change. The nether test's portal travel only force-generates the 17x17 square its exit portal scan reads (±128 blocks), so anything past view-distance 8 is pure overhead: on 1.8.8 the nether test drops from 6.7s to 4.4s with no other test affected. 8 is also the floor for the suite: blockfinder.js findBlocks uses maxDistance 128 (exactly 8 chunks), so lower values break the example tests. * ci: drop the unreleased-fixes install step minecraft-wrap 1.10.0 and minecraft-protocol 1.68.0 ship these fixes, so the plain npm install already picks them up via ^1.3.0 and ^1.67.0.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
…0ms (#4013) * ci: install minecraft-wrap from the concurrent-download fix commit minecraft-wrap 1.9.0 writes the version manifest to a path shared by the four per-version test processes and reads it back, so they tear each other's writes and fail in the before-all hook with JSON parse errors. Install PrismarineJS/node-minecraft-wrap#111 until it is released. * ci: also install minecraft-protocol from the ping close fix commit The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released. * Poll for the sign text echo in the nether test instead of sleeping 500ms Also flattens the test into a linear async flow with no raw bot._client listener: by the time placeBlock's block update echo has arrived the server has already opened the sign editor, so the text can be sent right away — and assertion failures now reject the test naturally instead of throwing inside a callback. * Emit blockEntityData and signOpen events and use them in the nether test (#3993) * Emit blockEntityData and signOpen instead of keeping packets internal Block entity updates (modern block_entity_data and legacy update_sign) were stored in the world silently, and the open_sign_entity packet was not surfaced at all, so consumers had to poll bot.blockAt or reach into bot._client. Emit a blockEntityData event with the updated block and a signOpen event with the placed sign instead. * nether test: use the signOpen and blockEntityData events Replaces the sign text polling loop with a wait for blockEntityData, and takes the placed sign from the new signOpen event instead of reading it out of the world by position. * ci: drop the unreleased-fixes install step minecraft-wrap 1.10.0 and minecraft-protocol 1.68.0 ship these fixes, so the plain npm install already picks them up via ^1.3.0 and ^1.67.0. * Revert the #3993 squash merge The blockEntityData/signOpen work belongs to its own PR (#4015); this branch should carry only the sign-poll change.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
…wers (#3976) The 26.1 external tests fail intermittently in CI with a bare `Timeout of 120000ms exceeded` in the `before all` hook, after printing `pinging 26.1 port : N` and never printing `pong`. It is the only version this happens on: over the last 40 failed CI runs the signature appears 4 times, all on 26.1, never on the other 27 versions. The server is not answering status yet. A server closes status connections without replying until it has built the status snapshot it serves, which does not happen until startup has fully finished — slightly after the "Done" line is logged. 26.1 widened that gap: it saves every dimension after logging "Done" and before finishing startup, which is the `Saving chunks for level ...` line only 26.1 prints after `Done`. Since minecraft-wrap calls back on that line, we ping inside the gap: probing every 25ms across a boot, 26.1 closes status connections until 52ms past `Done` and first answers at 77ms, while 1.21.8 already answers 11ms before it. Retry the ping instead of trusting the first one, with a closeTimeout short enough that a failed attempt is worth retrying. A fixed sleep would be tuned to today's startup sequence and would break the next time a version adds work after `Done`. mc.ping reports this as ETIMEDOUT after its whole closeTimeout, because it never notices the connection closing; PrismarineJS/node-minecraft-protocol#1514 fixes that upstream and makes each failed attempt immediate.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 27, 2026
…ge (#4007) * ci: install minecraft-wrap from the concurrent-download fix commit minecraft-wrap 1.9.0 writes the version manifest to a path shared by the four per-version test processes and reads it back, so they tear each other's writes and fail in the before-all hook with JSON parse errors. Install PrismarineJS/node-minecraft-wrap#111 until it is released. * ci: also install minecraft-protocol from the ping close fix commit The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released. * test: confirm superflat fills via chat echo instead of sleeping 100ms The six /fill repairs in resetBlocksToSuperflat are fire-and-forget, synchronized only by a fixed 100ms sleep — an assumption a slow CI tick can exceed. A marker chat message sent on the same ordered connection is echoed back only after the fills have executed and their block updates have already arrived, so waiting for the echo is both faster and an actual guarantee. Measured on 1.8.8: the fills step of the pre-test reset went from ~102ms avg to ~47ms avg (max 66ms). * chat: on 1.19.0, wait for queued commands before sending a chat message 1.19.0 runs its chat ordering check for commands on the main thread after queueing the packet, but for chat messages directly on the netty thread. A message sent while a command is still queued records its timestamp first, and the server then kicks the bot for that command with multiplayer.disconnect.out_of_order_chat. tab_complete is queued to the main thread the same way and always answered, so a message that follows an unacknowledged command first waits for a tab_complete reply. Every send on this version goes through one chain so ordering is preserved. * chat: gate the 1.19.0 command wait on supportFeature * Bump minecraft-data to ^3.114.0 for chatCommandsQueuedToMainThread * Drop the unreleased-dependency install step from CI
rom1504
pushed a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 28, 2026
…#3996) * test: confirm superflat fills via chat echo instead of sleeping 100ms The six /fill repairs in resetBlocksToSuperflat are fire-and-forget, synchronized only by a fixed 100ms sleep — an assumption a slow CI tick can exceed. A marker chat message sent on the same ordered connection is echoed back only after the fills have executed and their block updates have already arrived, so waiting for the echo is both faster and an actual guarantee. Measured on 1.8.8: the fills step of the pre-test reset went from ~102ms avg to ~47ms avg (max 66ms). * chat: on 1.19.0, wait for queued commands before sending a chat message (#4007) * ci: install minecraft-wrap from the concurrent-download fix commit minecraft-wrap 1.9.0 writes the version manifest to a path shared by the four per-version test processes and reads it back, so they tear each other's writes and fail in the before-all hook with JSON parse errors. Install PrismarineJS/node-minecraft-wrap#111 until it is released. * ci: also install minecraft-protocol from the ping close fix commit The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released. * test: confirm superflat fills via chat echo instead of sleeping 100ms The six /fill repairs in resetBlocksToSuperflat are fire-and-forget, synchronized only by a fixed 100ms sleep — an assumption a slow CI tick can exceed. A marker chat message sent on the same ordered connection is echoed back only after the fills have executed and their block updates have already arrived, so waiting for the echo is both faster and an actual guarantee. Measured on 1.8.8: the fills step of the pre-test reset went from ~102ms avg to ~47ms avg (max 66ms). * chat: on 1.19.0, wait for queued commands before sending a chat message 1.19.0 runs its chat ordering check for commands on the main thread after queueing the packet, but for chat messages directly on the netty thread. A message sent while a command is still queued records its timestamp first, and the server then kicks the bot for that command with multiplayer.disconnect.out_of_order_chat. tab_complete is queued to the main thread the same way and always answered, so a message that follows an unacknowledged command first waits for a tab_complete reply. Every send on this version goes through one chain so ordering is preserved. * chat: gate the 1.19.0 command wait on supportFeature * Bump minecraft-data to ^3.114.0 for chatCommandsQueuedToMainThread * Drop the unreleased-dependency install step from CI
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 29, 2026
The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 29, 2026
The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 29, 2026
The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released.
rom1504
pushed a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 30, 2026
* ci: install minecraft-wrap from the concurrent-download fix commit minecraft-wrap 1.9.0 writes the version manifest to a path shared by the four per-version test processes and reads it back, so they tear each other's writes and fail in the before-all hook with JSON parse errors. Install PrismarineJS/node-minecraft-wrap#111 until it is released. * ci: also install minecraft-protocol from the ping close fix commit The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released. * test(bed): wait for chunks instead of sleeping a fixed 3s The bed test opened with `await bot.test.wait(3000)` and the comment "Wait a few seconds for chunks". mineflayer already exposes the event-based version of exactly that wait, so use it. This is both faster and more correct: the fixed sleep over-waits on a fast machine (the chunks are typically already loaded) and can silently under-wait on a slow one, where the following blockAt assertions would fail for a reason unrelated to beds. Measured against a real 26.1 server, test only: before 10922ms after 7239ms, 6912ms * test(bed): reduce comment to the constraint * ci: drop the unreleased-fixes install step minecraft-wrap 1.10.0 and minecraft-protocol 1.68.0 ship these fixes, so the plain npm install already picks them up via ^1.3.0 and ^1.67.0.
rom1504
pushed a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 30, 2026
…3999) * ci: install minecraft-wrap from the concurrent-download fix commit minecraft-wrap 1.9.0 writes the version manifest to a path shared by the four per-version test processes and reads it back, so they tear each other's writes and fail in the before-all hook with JSON parse errors. Install PrismarineJS/node-minecraft-wrap#111 until it is released. * ci: also install minecraft-protocol from the ping close fix commit The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released. * test(exampleBee): await the op confirmation instead of sleeping 2s The 2s sleep after /op bee waited for the op (and with it the spawn protection bypass) to apply, but the server confirms exactly that with a chat message. Awaiting the confirmation takes ~50ms on 1.8.8 and ~11ms on 26.1, and is a guarantee where the sleep was a guess: exampleBee drops from 5.8s to 3.9s, leaving mostly the flight itself. * Drop the unreleased-dependency install step from CI
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Aug 31, 2026
The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Sep 4, 2026
The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released.
u9g
added a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Sep 4, 2026
The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released.
rom1504
pushed a commit
to PrismarineJS/mineflayer
that referenced
this pull request
Sep 5, 2026
…domly; fix rejected quick-move clicks on 1.12–1.16 (#3979) * ci: install minecraft-wrap from the concurrent-download fix commit minecraft-wrap 1.9.0 writes the version manifest to a path shared by the four per-version test processes and reads it back, so they tear each other's writes and fail in the before-all hook with JSON parse errors. Install PrismarineJS/node-minecraft-wrap#111 until it is released. * ci: also install minecraft-protocol from the ping close fix commit The external test's status-ping retry only helps if a closed status connection rejects promptly; released minecraft-protocol waits out the whole closeTimeout instead. Install PrismarineJS/node-minecraft-protocol#1514 until it is released. * test(useChests): lower the mouse-click fuzz from 250 to 50 Each bot.clickWindow waits for the server's transaction confirmation, which arrives on the next tick, so the click loop cannot run faster than 50ms per click on versions that acknowledge every click. A packet trace on 1.8.8 shows 366 window_click packets answered 1:1 by transaction packets, spanning 32149ms of the test's 37898ms — 85% of it. The version tiers (38.9s up to 1.13.2, 17.6s through 1.16.5, 12.4s from 1.17) track changes to that acknowledgement protocol, not anything in the test. Measured on 1.8.8, test duration only: before 38323ms after 29217ms * test(useChests): vary the click mode in the fuzz loop The loop only ever sent mouseButton 0, mode 0, so it reached pick up, place, merge and swap and nothing else, however many iterations it ran. Alternate left click, right click and shift-click quick move. Modes 5 and 6 are assert-unimplemented in prismarine-windows and mode 3 is creative-only, so this is the reachable set from survival. Measured, test duration only: 1.8.8 29217ms -> 28904ms 26.1 14015ms * test(useChests): assert each click transition explicitly A left or right click resolves differently depending on whether the cursor is holding something and what is in the target slot, so a random click only reaches whichever case the current state happens to allow. Repeating it does not change that. Drive each case from its own precondition and assert the resulting slot and cursor contents: take a stack, place one, merge into a matching stack, swap with a different type, drop a whole stack, take half, and shift click out. The previous loop asserted nothing at all — it only failed if clickWindow threw, and from 1.17 there is no accept/reject to throw on, only waitForWindowUpdate. Measured, test duration only: 1.8.8 29721ms, 26.1 15644ms. * test(useChests): drop the clicks that assert nothing Of the 366 window clicks the test made, ~350 asserted nothing: the random layout filled ~51 slots purely so the random click loop had something to click, and that loop had no assertions of its own — from 1.17 there is not even an accept/reject for it to throw on. The transition cases need one stack of more than 2, a second stack of a different type, and a free slot, so place three items deterministically and drop the fuzz loop. Right click and shift click stay covered, with assertions, in testClickTransitions. Measured, test duration only: 1.8.8 38323ms -> 14746ms (366 clicks -> 33) 26.1 14376ms * test(useChests): fill the chest server side, not by clicking Filling it with /give plus moveSlotItem leaves the result at the mercy of the client's predicted state. From 1.17 a click is not confirmed per transaction, so a lost move is invisible until an assertion reads the slot, and on 26.1 two of the three items silently never arrived: the chest held only the last one. The previous ~51-item random layout hid this — with that many items, losing a few still left plenty satisfying the preconditions. Three items removed the margin. Write the slots with /item replace block (1.17+), /replaceitem block (1.13+) or slot.container (older) instead, and assert the contents once the window opens, so a wrong command fails loudly rather than silently under-filling. Measured on 1.8.8: 13777ms. * test(useChests): find the filled items instead of assuming which half of the double chest lists first * fix: send an empty item for quick-move clicks on 1.12-1.16 so the server accepts them For actionIdUsed versions the server accepts a click only if the packet's item matches the result of its own slotClick. For a mode 1 quick move that is the moved stack up to 1.11 and empty from 1.12, and mineflayer always sent the clicked stack, so every shift click was rejected on 1.12-1.16. The old useChests fuzz never sent mode 1, so nothing noticed. * test(useChests): turn instantly openContainer and placeBlock face the block before acting, and a non-forced look turns at 3 rad/s, so the 180 degree swings between the chests cost about 1s per open. Nothing here asserts on looking. Measured, test duration only, 1.8.8: 13445ms -> 3475ms. * Drop the unreleased-dependency install step from CI * Use supportFeature instead of version comparisons quickMoveClickSendsEmptyItem, hasItemCommand and replaceItemSlotIsPrefixed are added in PrismarineJS/minecraft-data#1262.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
ping()settles on exactly three things:server_info,ping, or a clienterror. A server that accepts the connection and then closes it without answering is invisible to all three — the promise sits untilcloseTimeout(120 s by default) and then rejects withETIMEDOUT, blaming a timeout that never actually happened, long after the server already told us it wasn't going to answer.This isn't hypothetical: vanilla servers do it on every startup. A status request is closed without a reply — no packet, just a FIN — until the server has built the status snapshot it serves, and that doesn't happen until startup has fully finished, slightly after the
Done (…)!line is logged.So pinging a server the moment it reports being up can land in that gap. Probing a real server every 25 ms across a boot: 26.1 closes status connections until 52 ms after the
Doneline and first answers at 77 ms (it saves every dimension in between), while 1.21.8 is already answering 11 ms before it.This surfaces downstream as mineflayer's external tests intermittently failing on 26.1 with a bare mocha hook timeout (PrismarineJS/mineflayer#3976).
Fix
Reject on
endwhen no status has arrived yet:If the status did arrive and the server closes before the pong, the existing
noPongTimeoutpath already resolves with the status — that case is deliberately left alone, so this is only ever converting a 120 s misleading timeout into an immediate, accurate error.Test
test/pingTest.jspoints a ping at a socket server that accepts the handshake and hangs up. Without the fix it fails after the fullcloseTimeoutwithETIMEDOUT; with it, it fails in ~400 ms with a message that says what happened.