- VSCode Version: Version: 1.125.0 (Universal)
- Local OS Version: macOS 26.0
- Remote OS Version: Debian GNU/Linux 13 (trixie)
- Remote Extension/Connection Type: Dev Containers (local via podman + lima)
- Logs: Chrome: "net::ERR_INCOMPLETE_CHUNKED_ENCODING", curl: "curl: (18) transfer closed with outstanding read data remaining"
Steps to Reproduce:
HTTP requests served with "Transfer-Encoding: chunked" are randomly not transmitted in full from container to host (because the bridge terminates prematurely).
Below I'm attaching a nodeJs server and client script to reproduce.
- run
node server.js inside the devcontainer.
- run
node client.js outside the devcontainer on the host with port forwarding in vscode enabled.
- The client will encounter a varying number of ECONNRESET errors (for me between 25 and 40 in 200 requests)
Does this issue occur when you try this locally?: With local containers: Yes, Locally without containers: No, With client + server both inside the container: No
Does this issue occur when you try this locally and all extensions are disabled?: Did not try - I successfully fixed the bug by fixing the compiled ms-vscode-remote.remote-containers extension on my machine.
server.js
const http = require('http');
const { Readable } = require('stream');
const PORT = parseInt(process.env.PORT || '3030', 10);
const CHUNK_SIZE = parseInt(process.env.CHUNK_SIZE || '16384', 10);
const TOTAL_BYTES = parseInt(process.env.TOTAL_BYTES || String(8 * 1024 * 1024), 10);
const SENTINEL = '__END_OF_STREAM__';
const EXPECTED_BODY_BYTES = TOTAL_BYTES + Buffer.byteLength(SENTINEL);
const CHUNK = Buffer.alloc(CHUNK_SIZE, 0x41 /* 'A' */);
// Generator-backed body for chunked transfer
function* body() {
for (let sent = 0; sent < TOTAL_BYTES; sent += CHUNK_SIZE) {
yield sent + CHUNK_SIZE <= TOTAL_BYTES ? CHUNK : CHUNK.subarray(0, TOTAL_BYTES - sent);
}
yield Buffer.from(SENTINEL);
}
http
.createServer((req, res) => {
req.resume(); // drain any request body
res.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Cache-Control': 'no-store',
'Transfer-Encoding': 'chunked',
'X-Expected-Body-Bytes': String(EXPECTED_BODY_BYTES),
// Close after each response so every request exercises the bridge's
// connection-close handling — where the truncation occurs.
'Connection': 'close',
});
Readable.from(body()).pipe(res);
})
.listen(PORT, '0.0.0.0', () => {
console.log(
`well-behaved chunked server listening on 0.0.0.0:${PORT} | ` +
`total=${TOTAL_BYTES} bytes | chunk=${CHUNK_SIZE} | expectedBody=${EXPECTED_BODY_BYTES}`
);
});
client.js
const http = require('http');
const HOST = process.env.HOST || '127.0.0.1';
const PORT = parseInt(process.env.PORT || '3030', 10);
const COUNT = parseInt(process.env.COUNT || '200', 10);
const SENTINEL = '__END_OF_STREAM__';
const SENTINEL_LEN = Buffer.byteLength(SENTINEL);
let failures = 0;
let truncations = 0;
let errors = 0;
function makeRequest(i) {
return new Promise((resolve) => {
const req = http.get(
{ host: HOST, port: PORT, path: '/', headers: { Connection: 'close' } },
(res) => {
const expected = parseInt(res.headers['x-expected-body-bytes'] || '0', 10);
const chunks = [];
let aborted = false;
res.on('data', (c) => chunks.push(c));
// Node emits 'aborted' / an error with code ERR_INCOMPLETE_CHUNKED_ENCODING
// when a chunked response is closed before the terminating chunk arrives.
res.on('aborted', () => {
aborted = true;
});
res.on('error', (err) => {
aborted = true;
errors++;
failures++;
console.error(`[${i}] RES ERROR ${err.code || err.message}`);
});
res.on('end', () => {
const body = Buffer.concat(chunks);
const tail = body.subarray(Math.max(0, body.length - SENTINEL_LEN)).toString('latin1');
const sentinelOk = tail === SENTINEL;
const lengthOk = body.length === expected;
if (aborted || !sentinelOk || !lengthOk) {
failures++;
truncations++;
console.error(
`[${i}] TRUNCATED got=${body.length} expected=${expected} ` +
`sentinelOk=${sentinelOk} aborted=${aborted}`
);
}
resolve();
});
res.on('close', () => {
if (aborted && chunks.length >= 0) {
// 'end' may not fire on a hard abort; ensure we still resolve.
resolve();
}
});
}
);
req.on('error', (err) => {
errors++;
failures++;
console.error(`[${i}] REQ ERROR ${err.code || err.message}`);
resolve();
});
});
}
async function main() {
console.log(`Requesting http://${HOST}:${PORT}/ x${COUNT} ...`);
const start = Date.now();
for (let i = 0; i < COUNT; i++) {
await makeRequest(i);
}
const secs = ((Date.now() - start) / 1000).toFixed(1);
console.log('-----------------------------------------------');
console.log(`Done in ${secs}s | requests=${COUNT}`);
console.log(`failures=${failures} (truncations=${truncations}, errors=${errors})`);
if (failures > 0) {
console.log('REPRODUCED: at least one response was truncated / prematurely closed.');
process.exitCode = 1;
} else {
console.log('No truncation observed in this run. Try increasing COUNT or TOTAL_BYTES.');
}
}
main();
devcontainer.json
{
"name": "chunked-forwarding-repro",
"image": "mcr.microsoft.com/devcontainers/javascript-node:20",
"forwardPorts": [3030],
"portsAttributes": {
"3030": {
"label": "chunked-server",
"onAutoForward": "silent"
}
},
"postCreateCommand": "node --version"
}
The bug is in the inline script that the extension runs in the container.
Faulty code in the devcontainers extension:
const net = require('net');
const fs = require('fs');
process.stdin.pause();
const client = net.createConnection({ host: '127.0.0.1', port: ${o} }, () => {
console.error('Connection established');
client.pipe(process.stdout);
process.stdin.pipe(client);
});
client.on('close', function (hadError) {
console.error(hadError ? 'Remote close with error' : 'Remote close');
process.exit(hadError ? 1 : 0); // <<<- This does not wait for the pipe to drain
});
client.on('error', function (err) {
process.stderr.write(err && (err.stack || err.message) || String(err));
});
process.stdin.on('close', function (hadError) {
console.error(hadError ? 'Remote stdin close with error' : 'Remote stdin close');
process.exit(hadError ? 1 : 0);
});
process.on('uncaughtException', function (err) {
fs.writeSync(process.stderr.fd, \`Uncaught Exception: \${String(err && (err.stack || err.message) || err)}\\n\`);
});
Verified fix:
const net = require('net');
const fs = require('fs');
process.stdin.pause();
const client = net.createConnection({ host: '127.0.0.1', port: ${o} }, () => {
console.error('Connection established');
client.pipe(process.stdout);
process.stdin.pipe(client);
});
client.on('close', function (hadError) {
console.error(hadError ? 'Remote close with error' : 'Remote close');
// Wait for drainage
if (!process.stdout.writableEnded && !process.stdout.destroyed) {
process.stdout.end();
}
if (process.stdout.writableFinished) {
process.exit(hadError ? 1 : 0);
}
Promise.race([
new Promise(resolve => process.stdout.once('finish', resolve)),
new Promise((_, reject) => process.stdout.once('error', reject)),
]).then(() => process.exit(hadError ? 1 : 0),() => process.exit(1));
});
client.on('error', function (err) {
process.stderr.write(err && (err.stack || err.message) || String(err));
});
process.stdin.on('close', function (hadError) {
console.error(hadError ? 'Remote stdin close with error' : 'Remote stdin close');
process.exit(hadError ? 1 : 0);
});
process.on('uncaughtException', function (err) {
fs.writeSync(process.stderr.fd, \`Uncaught Exception: \${String(err && (err.stack || err.message) || err)}\\n\`);
});
Steps to Reproduce:
HTTP requests served with "Transfer-Encoding: chunked" are randomly not transmitted in full from container to host (because the bridge terminates prematurely).
Below I'm attaching a nodeJs server and client script to reproduce.
node server.jsinside the devcontainer.node client.jsoutside the devcontainer on the host with port forwarding in vscode enabled.Does this issue occur when you try this locally?: With local containers: Yes, Locally without containers: No, With client + server both inside the container: No
Does this issue occur when you try this locally and all extensions are disabled?: Did not try - I successfully fixed the bug by fixing the compiled ms-vscode-remote.remote-containers extension on my machine.
server.jsclient.jsdevcontainer.json{ "name": "chunked-forwarding-repro", "image": "mcr.microsoft.com/devcontainers/javascript-node:20", "forwardPorts": [3030], "portsAttributes": { "3030": { "label": "chunked-server", "onAutoForward": "silent" } }, "postCreateCommand": "node --version" }The bug is in the inline script that the extension runs in the container.
Faulty code in the devcontainers extension:
Verified fix: