Note: This is ovbiously written with AI after a lot of debugging. I generally write these myself, but it's been a long day. I went with manually patching compose.js:27 as a workaround which works correctly on my setup.
Summary
@balena/compose-parser@0.2.0 (shipped with balena-cli@25.1.0) spawns balena-compose-parser.exe via a shell exec() call with the binary path interpolated unquoted. On Windows installs whose path contains a space (notably the default C:\Program Files\balena-cli\ location used by the official installer), cmd.exe parses the path with the space as a separator and fails to find the command. The error then surfaces deep in the stack as Unexpected end of JSON input.
This breaks any command that parses the compose file — balena push, balena deploy, etc.
Reproduction
- Install
balena-cli@25.1.0 to C:\Program Files\balena-cli\ (the default for the Windows installer).
- Run
balena push <fleet> from a project that has a docker-compose.yml.
Expected
The compose file is parsed and the build is uploaded.
Actual
Could not parse stderr line as JSON: 'C:\Program' is not recognized as an internal or external command,
Could not parse stderr line as JSON: operable program or batch file.
Error parsing composition file "C:\path\to\project\docker-compose.yml":
Unexpected end of JSON input
SyntaxError: Error parsing composition file "...":
Unexpected end of JSON input
at JSON.parse (<anonymous>)
at Object.parse (C:\Program Files\balena-cli\client\node_modules\@balena\compose-parser\dist\compose.js:43:31)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async parseComposePaths (C:\Program Files\balena-cli\client\build\utils\compose_ts.js:66:16)
at async getServiceDirsFromComposition (C:\Program Files\balena-cli\client\build\utils\compose_ts.js:358:27)
at async tarDirectory (C:\Program Files\balena-cli\client\build\utils\compose_ts.js:389:25)
at async getTarStream (C:\Program Files\balena-cli\client\build\utils\remote-build.js:210:27)
at async getRemoteBuildStream (C:\Program Files\balena-cli\client\build\utils\remote-build.js:274:23)
at async Object.startRemoteBuild (C:\Program Files\balena-cli\client\build\utils\remote-build.js:39:36)
at async PushCmd.pushToCloud (C:\Program Files\balena-cli\client\build\commands\push\index.js:78:27)
The 'C:\Program' is not recognized as an internal or external command line is cmd.exe trying to execute the unquoted path with a space.
Root cause
In @balena/compose-parser/dist/compose.js:
const binaryName = process.platform === 'win32'
? 'balena-compose-parser.exe'
: 'balena-compose-parser';
const binaryPath = path.join(__dirname, '..', 'bin', binaryName);
const result = await exec(`${binaryPath} ${fileFlags} ${projectName}`, {
env: process.env,
});
binaryPath resolves to e.g. C:\Program Files\balena-cli\client\node_modules\@balena\compose-parser\bin\balena-compose-parser.exe. It's interpolated raw into a child_process.exec() shell command, so cmd /c sees:
C:\Program Files\balena-cli\...\balena-compose-parser.exe -f ... <uuid>
and parses C:\Program as the command name. The subprocess never starts, stdout is empty, JSON.parse('') throws Unexpected end of JSON input at line 43 of the same file.
The bug is independent of the project's docker-compose.yml content — the file is never read by the failing process.
Suggested fix
Two options, in order of preference:
1. Use execFile instead of exec — avoids shell quoting entirely:
const { execFile } = require('child_process');
const execFileP = promisify(execFile);
// ...
const args = [...filePaths.flatMap(p => ['-f', p]), projectName];
const result = await execFileP(binaryPath, args, { env: process.env });
2. Quote binaryPath in the shell command — minimal change:
-const result = await exec(`${binaryPath} ${fileFlags} ${projectName}`, {
+const result = await exec(`"${binaryPath}" ${fileFlags} ${projectName}`, {
Note that ${fileFlags} (which contains user-supplied compose paths) is also unquoted and will hit the same issue if a user's project lives under a path with spaces (e.g. C:\Users\Some Name\project\). A robust fix should quote those too, or better, switch to execFile and pass arguments as an array.
Workaround
Manually patch compose.js:27 per option 2 above, OR reinstall balena-cli to a path without spaces (e.g. C:\balena-cli\).
Environment
- balena-cli: 25.1.0 (
win32-x64, bundled Node v24.14.1)
@balena/compose-parser: 0.2.0
- OS: Windows 11, build 10.0.26200.8313
- Install location:
C:\Program Files\balena-cli\ (default for the official Windows installer)
Note: This is ovbiously written with AI after a lot of debugging. I generally write these myself, but it's been a long day. I went with manually patching
compose.js:27as a workaround which works correctly on my setup.Summary
@balena/compose-parser@0.2.0(shipped withbalena-cli@25.1.0) spawnsbalena-compose-parser.exevia a shellexec()call with the binary path interpolated unquoted. On Windows installs whose path contains a space (notably the defaultC:\Program Files\balena-cli\location used by the official installer),cmd.exeparses the path with the space as a separator and fails to find the command. The error then surfaces deep in the stack asUnexpected end of JSON input.This breaks any command that parses the compose file —
balena push,balena deploy, etc.Reproduction
balena-cli@25.1.0toC:\Program Files\balena-cli\(the default for the Windows installer).balena push <fleet>from a project that has adocker-compose.yml.Expected
The compose file is parsed and the build is uploaded.
Actual
The
'C:\Program' is not recognized as an internal or external commandline iscmd.exetrying to execute the unquoted path with a space.Root cause
In
@balena/compose-parser/dist/compose.js:binaryPathresolves to e.g.C:\Program Files\balena-cli\client\node_modules\@balena\compose-parser\bin\balena-compose-parser.exe. It's interpolated raw into achild_process.exec()shell command, socmd /csees:and parses
C:\Programas the command name. The subprocess never starts,stdoutis empty,JSON.parse('')throwsUnexpected end of JSON inputat line 43 of the same file.The bug is independent of the project's
docker-compose.ymlcontent — the file is never read by the failing process.Suggested fix
Two options, in order of preference:
1. Use
execFileinstead ofexec— avoids shell quoting entirely:2. Quote
binaryPathin the shell command — minimal change:Note that
${fileFlags}(which contains user-supplied compose paths) is also unquoted and will hit the same issue if a user's project lives under a path with spaces (e.g.C:\Users\Some Name\project\). A robust fix should quote those too, or better, switch toexecFileand pass arguments as an array.Workaround
Manually patch
compose.js:27per option 2 above, OR reinstall balena-cli to a path without spaces (e.g.C:\balena-cli\).Environment
win32-x64, bundled Nodev24.14.1)@balena/compose-parser: 0.2.0C:\Program Files\balena-cli\(default for the official Windows installer)