Skip to content

Commit af2d1d1

Browse files
rom1504claude
andauthored
Update CI to Node 24 + fix async zlib crashes (#705)
* Update CI to Node 24 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix spawn and velocity packets to use vec3i16 velocity field The protocol schema expects a compound `velocity` field of type vec3i16 ({x, y, z}) for spawn_entity, spawn_entity_living, and entity_velocity packets. The code was only sending separate velocityX/Y/Z fields which are not recognized by the current minecraft-data protocol definitions, causing client disconnections and test timeouts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Retrigger CI after node-minecraft-protocol release * fix: disconnect bots before server quit to avoid Node 24 zlib error In Node 24, the stricter zlib implementation throws an uncaught "unexpected end of file" error when the server force-kicks clients, truncating the compressed protocol stream mid-packet. By gracefully disconnecting the bots before stopping the server, the compression stream is drained cleanly and the error is avoided. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix afterEach: use timeout instead of waiting for end event once(bot, 'end') can hang if the socket is already closed. Use bot.quit() + 500ms sleep instead of waiting for the end event. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Suppress zlib uncaught errors from Node 24 stricter decompression Node 24's zlib throws "unexpected end of file" when compressed data is truncated during connection/disconnection. This is not a test failure — catch and suppress these specific errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Use minecraft-protocol zlib fix branch, remove workaround Point to PrismarineJS/node-minecraft-protocol#fix-zlib-node24 which fixes the root cause of zlib crashes on Node 24. Remove the process.on('uncaughtException') workaround. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Re-add uncaughtException handler for streaming zlib errors The try/catch in minecraft-protocol fixes callback-based zlib.unzip(), but the streaming Zlib layer (used internally by Node for connection compression) emits errors via 'onerror' callback which becomes an uncaught exception. Need both fixes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Remove uncaughtException workaround The zlib errors are now properly handled in minecraft-protocol: - try/catch for sync throws in zlib.unzip/deflate - Suppress errors during client shutdown (this.ended check) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Retrigger CI with sync zlib fix in NMP * Retrigger CI with sync zlib in NMP (cache bust) * Re-add uncaughtException handler for deep zlib errors The sync zlib fix in NMP handles compression.js, but prismarine-nbt uses async zlib.gunzip() which can still throw uncaught on Node 24 when world data is truncated during test teardown. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Use prismarine-nbt zlib fix, remove uncaughtException handler Point to PrismarineJS/prismarine-nbt#fix-zlib-node24 which uses gunzipSync to prevent uncaught async zlib errors on Node 24. Remove the process.on('uncaughtException') workaround. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Retrigger CI with prismarine-nbt fix in dep chain * Retrigger with fixed prismarine-nbt * Use prismarine-provider-anvil zlib fix, replace node-gzip with native zlib - Point to PrismarineJS/prismarine-provider-anvil#fix-zlib-node24 - Remove node-gzip dependency, use native zlib.gzip with try/catch - All async zlib calls in the dependency chain now have try/catch for Node 24's synchronous throws Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add --retries 3 to CI for transient zlib errors on Node 24 Node 24's internal C++ zlib binding can throw uncaught async errors during connection teardown that we can't catch from JS. These are transient — retrying the test passes. Add --retries 3 to mocha. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Retry mocha at process level for uncaught zlib errors Mocha's --retries doesn't retry uncaught exceptions. The Node 24 zlib error is an uncaught exception that crashes the test but not the process. Retry the whole mocha run up to 3 times at the shell level instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Use sync gzip, remove bash retry workaround All zlib calls in the dependency chain now use sync versions, eliminating the uncaught async errors. No need for process-level retries. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Revert to normal deps, keep node-gzip removal + async gzip The zlib fix is only needed in NMP (destroyed-state checks). No sync zlib needed anywhere. Reverted all GitHub branch deps. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Depend on NMP fix branch for destroyed-state zlib checks The uncaught zlib errors during disconnect are fixed by this.destroyed checks in NMP's compression streams. Need to point to the fix branch until NMP releases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Use sync gzip in playerDat + NMP sync zlib fix Use sync zlib to avoid uncaught async errors from Node's C++ zlib binding race condition during client disconnect. See: - nodejs/node#62325 (use-after-free in zlib) - nodejs/node#61202 (zlib stream corruption) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Retrigger CI * Depend on all 3 sync zlib fix branches The uncaught zlib errors come from NMP, prismarine-nbt, AND prismarine-provider-anvil. All need sync zlib. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Use released versions of sync zlib dependencies - minecraft-protocol ^1.66.0 - prismarine-nbt ^2.8.0 - prismarine-provider-anvil ^2.13.0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: rom1504 <rom1504@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 52787f7 commit af2d1d1

6 files changed

Lines changed: 21 additions & 8 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- name: Use Node.js 22.x
1616
uses: actions/setup-node@v1.4.4
1717
with:
18-
node-version: 22.x
18+
node-version: 24
1919
- run: npm i && npm run lint
2020

2121
PrepareTestedVersions:
@@ -28,7 +28,7 @@ jobs:
2828
- name: Use Node.js 22.x
2929
uses: actions/setup-node@v1.4.4
3030
with:
31-
node-version: 22.x
31+
node-version: 24
3232
- id: set-matrix
3333
run: |
3434
node -e "
@@ -48,7 +48,7 @@ jobs:
4848
- name: Use Node.js 22.x
4949
uses: actions/setup-node@v1.4.4
5050
with:
51-
node-version: 22.x
51+
node-version: 24
5252
- name: Install Dependencies
5353
run: npm install
5454
- name: Start Tests

package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,14 @@
3535
"exit-hook": "^2.2.1",
3636
"flatmap": "^0.0.3",
3737
"long": "^5.1.0",
38-
"minecraft-protocol": "^1.53.0",
38+
"minecraft-protocol": "^1.66.0",
3939
"moment": "^2.10.6",
4040
"needle": "^2.5.0",
41-
"node-gzip": "^1.1.2",
4241
"prismarine-chunk": "^1.34.0",
4342
"prismarine-entity": "^2.2.0",
4443
"prismarine-item": "^1.14.0",
45-
"prismarine-nbt": "^2.2.1",
46-
"prismarine-provider-anvil": "^2.7.0",
44+
"prismarine-nbt": "^2.8.0",
45+
"prismarine-provider-anvil": "^2.13.0",
4746
"prismarine-registry": "^1.7.0",
4847
"prismarine-windows": "^2.8.0",
4948
"prismarine-world": "^3.6.2",

src/lib/playerDat.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ const fs = require('fs')
44
const Vec3 = require('vec3').Vec3
55
const nbt = require('prismarine-nbt')
66
const long = require('long')
7-
const { gzip } = require('node-gzip')
7+
const zlib = require('zlib')
88
const { promisify } = require('util')
99
const convertInventorySlotId = require('./convertInventorySlotId')
1010

11+
// Use sync gzip to avoid uncaught async zlib errors during teardown.
12+
// See nodejs/node#62325, nodejs/node#61202
13+
const gzip = (data) => Promise.resolve(zlib.gzipSync(data))
14+
1115
const nbtParse = promisify(nbt.parse)
1216

1317
const playerDefaults = {

src/lib/plugins/physics.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ module.exports.entity = function (entity, serv, { version }) {
4747
scaledVelocity = scaledVelocity.floored()
4848
entity._writeNearby('entity_velocity', {
4949
entityId: entity.id,
50+
velocity: scaledVelocity,
5051
velocityX: scaledVelocity.x,
5152
velocityY: scaledVelocity.y,
5253
velocityZ: scaledVelocity.z

src/lib/plugins/spawn.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ module.exports.entity = function (entity, serv) {
334334
headPitch: entity.headPitch,
335335
currentItem: 0,
336336
objectData: entity.data,
337+
velocity: scaledVelocity,
337338
velocityX: scaledVelocity.x,
338339
velocityY: scaledVelocity.y,
339340
velocityZ: scaledVelocity.z,

test/mineflayer.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,14 @@ squid.testedVersions.forEach((testedVersion, i) => {
159159

160160
afterEach(async () => {
161161
console.log('Quitting server...')
162+
// Disconnect bots before stopping the server so the compressed
163+
// stream is drained cleanly. Without this, Node 24's stricter
164+
// zlib can throw an uncaught "unexpected end of file" error when
165+
// the server force-kicks clients and truncates the compressed data.
166+
try { bot?.quit() } catch (e) { /* ignore */ }
167+
try { bot2?.quit() } catch (e) { /* ignore */ }
168+
// Give bots time to disconnect cleanly
169+
await new Promise(resolve => setTimeout(resolve, 500))
162170
await serv.quit()
163171
console.log('Quit server!')
164172
})

0 commit comments

Comments
 (0)