Skip to content

LocalPollingProvider splits script ref on whitespace — any executable path containing a space fails to spawn #1794

Closed

Description

@bradygaster

Found while building .squad/e2e/windows-test-baseline.md (EECOM, baseline commit 369bba8f).

Summary

LocalPollingProvider.execute() parses a script task's ref by splitting on whitespace with no quote or escape handling. Any command whose executable path contains a space is therefore unrunnable — which on Windows includes the default Node install (C:\Program Files\nodejs\node.exe) and anything under C:\Program Files.

This is a genuine cross-platform defect. It was previously categorized as "scheduler timing", which it is not.

Mechanism

packages/squad-sdk/src/runtime/scheduler.ts, LocalPollingProvider.execute, case 'script':

const argv = entry.task.ref.trim().split(/\s+/);
const command = argv[0]!;
const args = argv.slice(1);
const { stdout } = await execFileAsync(command, args, { ... });

Given ref = "C:\Program Files\nodejs\node.exe -e console.log('hi')" this yields:

  • command = "C:\Program"
  • args = ["Files\nodejs\node.exe", "-e", "console.log('hi')"]

execFile then fails with ENOENT. Because the failure is a spawn failure rather than a non-zero exit, the "rich error capture" path surfaces code: undefined and stderr: '' — so the provider reports a failure that carries no diagnostic information at all.

Linux CI never sees this because process.execPath there is /usr/bin/node — no spaces.

Controlled proof

test/scheduler.test.ts builds its ref from process.execPath:

task: { type: 'script', ref: `${process.execPath} -e console.log('hello-from-scheduler')` },

On Windows, 4 tests in Scheduler: LocalPollingProvider fail:

× should execute script tasks              expected false to be true
× captures the exit code on a non-zero exit expected undefined to be 7
× captures stderr written by the child      expected '' to contain 'boom-from-stderr'
× does not block the event loop             expected false to be true

The failure signature — success: false, code: undefined, stderr: '' — is exactly a spawn ENOENT, not a script that ran and failed.

Suggested fix

Two independent problems, both worth addressing:

  1. Parsing. Replace the naive split(/\s+/) with a parser that honours quoting, or change the task schema to accept a structured { command, args[] } form so no parsing is needed. The structured form is preferable — it also removes an injection-adjacent surface and keeps the existing shell: false safety property. validateTaskRef would need a matching update.
  2. Error capture. In the catch, distinguish spawn failure from non-zero exit and populate the result accordingly (e.g. surface err.code === 'ENOENT' as a distinct, described failure). A block titled "rich error capture on script failure" that returns code: undefined, stderr: '' for the most common real-world failure is misleading.

Success criteria

  • A script task whose executable path contains spaces executes successfully (add a test using a path with a space, so it fails on Linux too if the parser regresses).
  • All 4 Scheduler: LocalPollingProvider tests pass on Windows.
  • A spawn failure (ENOENT) produces a TaskResult that names the missing command, rather than code: undefined / stderr: ''.
  • The new test fails against current dev — verified red before green, per the standing rule.

Related

  • .squad/e2e/windows-test-baseline.md row 4.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions