Workflow({name: "<x>"}) silently resolves to the built-in workflow even when a same-named local .claude/workflows/<x>.js exists — no error, no indication

Status Open
Maintainer reply None cached
Activity 1 comment · opened Aug 31, 2026

Summary

The Workflow tool's own description states a name resolves to "a predefined workflow (built-in or from .claude/workflows/)", which reads as if a local project/user file can define or override a workflow of that name. In practice, when a built-in workflow and a local .claude/workflows/<name>.js share the same name, Workflow({name: "<name>", ...}) always runs the built-in and silently ignores the local file — no error, no warning, no indication in the tool output that the local file was skipped.

Repro

  1. Found a bug in the built-in deep-research workflow (StructuredOutput corruption in the Scope phase — same bug class as #79019 / #75086).
  2. Wrote a corrected version of the entire script (same meta.name: 'deep-research', one schema trimmed) to ~/.claude/workflows/deep-research.js.
  3. Called Workflow({name: "deep-research", args: "..."}).
  4. Confirmed via two independent signals that the built-in ran, not the local file:
  • Added a unique marker string to the local file's meta.description. The tool result's Summary: field (which echoes meta.description) never showed the marker.
  • Grepped the per-run script that gets persisted to the session's transcript dir (workflows/scripts/deep-research-<runId>.js) — it contained the ORIGINAL (unfixed) schema, not the edited one.
  1. Confirmed the local file itself is valid and independently loadable: Workflow({scriptPath: "/home/<user>/.claude/workflows/deep-research.js", args: "..."}) (explicit path, no name) correctly ran the fixed version — same marker technique, this time the marker did show up.

So the local file is syntactically fine and independently invocable — it's specifically the name → resolution step that never considers it when a built-in of the same name exists.

Expected

One of:

  • .claude/workflows/<name>.js should take precedence over (or at least be selectable instead of) a built-in of the same name, matching what the tool description implies, or
  • If built-ins are intentionally not overridable by design, the tool description should say so explicitly, and ideally Workflow({name: ...}) should warn (e.g. "a local file with this name exists but was not used because <name> is a reserved built-in") instead of silently discarding it.

Right now the failure mode is silent: a user (or an agent acting on their behalf) can reasonably believe they've patched a broken built-in workflow, verify the file is syntactically correct, and have every subsequent Workflow({name: ...}) call keep silently running the old broken version with zero signal that the fix isn't being used.

Side effect worth flagging

Skills that hardcode Invoke: Workflow({ name: "<x>", ... }) in their own instructions (e.g. the built-in deep-research skill) inherit this problem: even a perfectly-written local override can never be reached through the normal skill-triggered flow, only by manually calling Workflow({scriptPath: ...}) directly and bypassing the skill entirely.

Environment

  • Claude Code CLI v2.1.215, Linux.
  • Workflow tool, name vs scriptPath resolution.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗