Describe the bug
Detached/background runs fail on native Windows before an agent can complete. There are two sequential Windows-specific blockers in the current main implementation:
runnerModulePath() converts import.meta.url to a drive-letter path, but taskflow-core/detached-runner later passes that value to dynamic import(). Node's ESM loader rejects C:\... as protocol c:.
- After preserving the runner as a
file:// URL, the detached Node process falls back to spawn("pi", ...). A native npm installation exposes Pi through Windows shims (pi.cmd / pi.ps1), so the child fails with spawn pi ENOENT. Passing pi.cmd directly to spawn() without a shell produces EINVAL on Node 24.
To reproduce
On native Windows, install Pi and pi-taskflow@0.2.10, save a minimal flow, and start it detached:
{
"name": "windows-detached-repro",
"concurrency": 1,
"phases": [
{
"id": "probe",
"type": "agent",
"agent": "scout",
"task": "Return OK.",
"final": true
}
]
}
Invoke the Taskflow tool with:
{
"action": "run",
"name": "windows-detached-repro",
"detach": true
}
Then inspect the run through /tf runs or its persisted JSON state.
Expected behavior
The detached runner loads the Pi subagent runner and starts the configured Pi child on native Windows.
Observed behavior
The unmodified package persists this terminal failure:
Runner module failed to load: 'C:\...\pi-taskflow\dist\runner.js'
Only URLs with a scheme in: file, data, and node are supported by the default ESM loader.
On Windows, absolute paths must be valid file:// URLs. Received protocol 'c:'
Changing runnerModulePath() locally to return import.meta.url exposes the second blocker:
With both local changes applied—preserving the file:// runner URL and re-entering the installed Pi JS CLI via process.execPath on Windows—the same detached flow completes successfully. A three-phase read-only flow (script preflight, Pi worker, Pi review gate) also completed 3/3 in detached mode.
Environment
- pi-taskflow version:
0.2.10
- taskflow-core version:
0.2.10
- Pi version:
0.84.2
- Node version:
24.18.0
- OS: native Windows 11
Additional context
Current main still contains both relevant paths:
return { command: "pi", args };
and:
export function runnerModulePath(): string {
return fileURLToPath(import.meta.url);
}
Potential fixes:
-
Keep runnerModulePath() as a file:// URL, or normalize filesystem paths with pathToFileURL() immediately before dynamic import.
-
On Windows, resolve the actual installed Pi JS entry point and launch it with process.execPath, or serialize the initiating Pi CLI entry into the detached context. Avoid shell: true so arguments remain argv-safe.
-
Reproduces on a fresh detached run: yes
-
Foreground execution: works
-
Persisted detached state correctly records both failures; the issue is runner startup rather than state persistence.
Describe the bug
Detached/background runs fail on native Windows before an agent can complete. There are two sequential Windows-specific blockers in the current
mainimplementation:runnerModulePath()convertsimport.meta.urlto a drive-letter path, buttaskflow-core/detached-runnerlater passes that value to dynamicimport(). Node's ESM loader rejectsC:\...as protocolc:.file://URL, the detached Node process falls back tospawn("pi", ...). A native npm installation exposes Pi through Windows shims (pi.cmd/pi.ps1), so the child fails withspawn pi ENOENT. Passingpi.cmddirectly tospawn()without a shell producesEINVALon Node 24.To reproduce
On native Windows, install Pi and
pi-taskflow@0.2.10, save a minimal flow, and start it detached:{ "name": "windows-detached-repro", "concurrency": 1, "phases": [ { "id": "probe", "type": "agent", "agent": "scout", "task": "Return OK.", "final": true } ] }Invoke the Taskflow tool with:
{ "action": "run", "name": "windows-detached-repro", "detach": true }Then inspect the run through
/tf runsor its persisted JSON state.Expected behavior
The detached runner loads the Pi subagent runner and starts the configured Pi child on native Windows.
Observed behavior
The unmodified package persists this terminal failure:
Changing
runnerModulePath()locally to returnimport.meta.urlexposes the second blocker:With both local changes applied—preserving the
file://runner URL and re-entering the installed Pi JS CLI viaprocess.execPathon Windows—the same detached flow completes successfully. A three-phase read-only flow (script preflight, Pi worker, Pi review gate) also completed3/3in detached mode.Environment
0.2.100.2.100.84.224.18.0Additional context
Current
mainstill contains both relevant paths:and:
Potential fixes:
Keep
runnerModulePath()as afile://URL, or normalize filesystem paths withpathToFileURL()immediately before dynamic import.On Windows, resolve the actual installed Pi JS entry point and launch it with
process.execPath, or serialize the initiating Pi CLI entry into the detached context. Avoidshell: trueso arguments remain argv-safe.Reproduces on a fresh detached run: yes
Foreground execution: works
Persisted detached state correctly records both failures; the issue is runner startup rather than state persistence.