Skip to content

cwd does not support {args.*}/{steps.*} interpolation — no in-DSL escape hatch (unlike run) #70

Description

@blain3white

Problem

The cwd field explicitly rejects all interpolation placeholders ({args.*}, {steps.*}) at validation time, while nearly every other field supports them. There is no safe alternative form (unlike run, which has an array escape hatch).

Which fields support {args.*} / {steps.*} interpolation?

Field Interpolation Notes
task
context {steps.X} refs + file paths
input (script stdin)
over (map)
when
expect
def / with (flow)
until (loop)
run (string form) Rejected — shell injection guard
run (array form) Safe alternative: ["python3","x.py","{args.y}"] (execvp, no shell)
cwd Rejected — no alternative provided

Source

schema.js line 42-43, 464-465:

/** `cwd` is a literal path / workspace keyword, not an interpolated field. */
const CWD_PLACEHOLDER_RE = /\{[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*\}/;

if (typeof p.cwd === "string" && CWD_PLACEHOLDER_RE.test(p.cwd)) {
    errors.push(`Phase '${p.id}': 'cwd' does not support interpolation placeholders (${p.cwd}). Use a literal path, or a reserved workspace keyword ('temp', 'dedicated', 'worktree').`);
}

At runtime, runtime.js passes phase.cwd directly to spawn({cwd: ...}):

function resolveEffCwd(deps, phase) {
    return deps._cwdOverride
        ?? (isWorkspaceKeyword(phase.cwd) ? deps.cwd : phase.cwd ?? deps.cwd);
}

Node.js spawn accepts relative paths (resolved against the parent process cwd), so a runtime-resolved {args.repo_dir} would work fine — the issue is purely the validator blocking it.

Impact

Users whose flow needs a dynamic working directory (e.g. multi-repo monorepo where repo_dir is passed as an arg) cannot use cwd: "{args.repo_dir}". They must write an external render script that pre-processes the flow JSON, replacing {args.repo_dir} with a literal path before calling taskflow run.

This is the only field that requires such a workaround with no in-DSL escape hatch, which is surprising and adds friction.

Reproduction

{
  "name": "demo",
  "args": { "repo_dir": { "default": "packages/api" } },
  "phases": [
    {
      "id": "work",
      "type": "agent",
      "cwd": "{args.repo_dir}",
      "task": "Do work in {args.repo_dir}"
    }
  ]
}

action: "verify" → error:

Phase 'work': 'cwd' does not support interpolation placeholders ({args.repo_dir}).
Use a literal path, or a reserved workspace keyword ('temp', 'dedicated', 'worktree').

Meanwhile task in the same phase interpolates {args.repo_dir} just fine.

Proposal

Allow {args.*} (and ideally {steps.*}) interpolation in cwd, resolving placeholders before passing to spawn().

The run field's design is instructive: it blocks string-form interpolation (shell injection risk) but provides the array form as a safe alternative. cwd has no equivalent escape hatch — args are author-declared values (not untrusted user input), so allowing {args.*} at minimum would eliminate the most common workaround.

If full interpolation is too risky, consider a dedicated cwdArgs or allowing cwd to accept an object form like { "from": "args", "key": "repo_dir" } as a safe, bounded alternative.

Workaround

External render script that reads {args.repo_dir} and replaces it with a literal path before taskflow run. Works but adds a step and a script to maintain.

Environment

  • pi-taskflow: installed via file: link from packages/pi-taskflow
  • taskflow-core: local file dependency
  • pi-coding-agent: 0.80.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions