Workflow tool: args arrives JSON-stringified, not as object; named workflow edits don't hot-reload mid-session

Open 💬 2 comments Opened Jun 17, 2026 by Spectator85

Summary

Two related bugs found while debugging a custom saved workflow (~/.claude/workflows/devloop.js) invoked via Workflow({name: "devloop", args: {...}}).

Bug 1: args arrives as a JSON-encoded string, not a structured object

The docs (and the Workflow tool's own description) say to pass args as an actual JSON object/array value, not a JSON-encoded string:

Pass arrays/objects as actual JSON values in the tool call, NOT as a JSON-encoded string — args: ["a.ts", "b.ts"], not args: "[\"a.ts\", ...]" (a stringified list reaches the script as one string, so args.filter/args.map throw).

However, even when passed correctly as a JSON object in the tool call (e.g. args: {"task": "fix the bug in foo.js", "maxIterations": 4}), the script receives args as a string — i.e. typeof args === 'string', and args is literally the JSON-encoded text of the object, not the parsed object itself.

Repro

Minimal diagnostic workflow:

export const meta = {
  name: 'args-echo-debug',
  description: 'Echo back the args value for debugging',
  phases: [{ title: 'Echo' }],
}
return { argsReceived: args, typeofArgs: typeof args, taskValue: args ? args.task : 'args was falsy' }

Invoked with:

Workflow({
  script: "<above>",
  args: {"task": "fix the bug in C:\Users\...\add.js", "maxIterations": 4}
})

Result:

{
  "argsReceived": "{\"task\": \"fix the bug in C:\\Users\\...\\add.js\", \"maxIterations\": 4}",
  "typeofArgs": "string"
}

args is the JSON text of the object, not the object. This reproduced identically whether invoking by name (a saved workflow) or by inline script.

Impact

Any workflow script that does args.someField (as documented/recommended) silently gets undefined instead of throwing, because property access on a string doesn't error — it just returns undefined. This is hard to detect: the script runs to completion, just with wrong/missing data. In our case it caused a coder subagent to receive the literal prompt "Task: undefined" and loop "blocked" for every iteration until max retries were exhausted, returning null.

Workaround

Defensively normalize at the top of the script:

const input = (() => {
  if (args && typeof args === 'object') return args
  if (typeof args === 'string') { try { return JSON.parse(args) } catch (e) { return { task: args } } }
  return {}
})()

Bug 2: editing a saved named workflow mid-session doesn't take effect

After patching ~/.claude/workflows/devloop.js on disk (added the args normalization above) mid-session, re-invoking Workflow({name: "devloop", args: {...}}) in the same session still ran the pre-edit version of the script — confirmed by reading back the persisted per-run script file under the session directory, which showed the old, unpatched content.

Switching to passing the same corrected script inline via script (instead of name) immediately picked up the fix in the same session.

Impact

This makes it impossible to iterate on a saved/named workflow within a single session — edits to the source file are silently ignored until (presumably) a new session is started, with no warning that a stale cached copy is being used instead of the on-disk file.

Environment

  • Claude Code version: 2.1.179
  • OS: Windows 11
  • Dynamic workflows: enabled via /config

Expected behavior

  1. args should reach the script as the structured object/array passed in the tool call (matching the documented behavior), not as a JSON-encoded string.
  2. Editing a named workflow's source file should be reflected the next time it's invoked by name within the same session — or, if intentionally cached for performance, the tool/docs should clearly state this and ideally provide a way to force a reload short of starting a new session.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗