← blog.wrbriggs.cloud
DYNAMIC WORKFLOWS IN CLAUDE CODE · PART 4 OF 6

From One-Off Script to Team Asset: Saving, Versioning, and Shipping Workflows in Plugins

How to turn a good run into a named, parameterized, code-reviewed workflow: in the repo for your team, or in a plugin for your org.

Will Briggs·August 2026·13 min read

BEFORE YOU START

This post assumes part one and part two. Part three helps, mostly for the review checklist's vocabulary.

A teammate types this on a Tuesday:

/audit-routes on src/payments/

Thirty-some agents fan out, a verify phase kills the weak findings, and a ranked report lands in her session. She has never seen the script. She couldn't tell you what pipeline does. The person who wrote the workflow is on a beach with his phone off, and none of that matters, because the thing she invoked is software: named, versioned, reviewed, and documented at its boundary.

That's the whole subject of this post. Parts one through three produced a good script. This part makes it survive contact with other people. There are two destinations, a repo directory for your team and a plugin for your org, and by the end you'll know how to ship to both and when to pick which.

The persistence model

Saving is unceremonious. From the /workflows view, select a run and press s. Or skip the ceremony entirely and write the file yourself, because a saved workflow is one .js file, meta block plus body, exactly what you edited in part two. Nothing transforms it. The file you save is the file that runs.

Where it lives decides who can use it. Save to .claude/workflows/ in the project and it ships with the repo: everyone who clones gets it, it shows up in / autocomplete, and it goes through code review like any other file. Save to ~/.claude/workflows/ and it follows you across every project but stays invisible to everyone else (if you set CLAUDE_CONFIG_DIR, it lives under that path instead). When a project workflow and a personal one share a name, the project one runs.

Monorepos get one more rule, and it's the right one. Since v2.1.178, workflows load from every .claude/workflows/ directory between your working directory and the repo root, and the closest one wins a name collision. Saves write to the closest existing directory, or the root if none exists yet. So keep a package's workflows next to the package; the resolution order already agrees with you.

BARE NAME: /audit-routes · CHECKED IN ORDER, FIRST MATCH WINS
1  packages/api/.claude/workflows/ closest to your working directory
2  …every .claude/workflows/ up the path
3  <repo root>/.claude/workflows/ project scope, shared, reviewed
4  ~/.claude/workflows/ personal scope, only you
NAMESPACED: /acme-tools:release-audit
acme-tools plugin → workflows/ own lane; a namespaced name can't collide with a bare one
One invocation, one lookup. Bare names walk the project path, then personal scope; plugin workflows live in their own namespace entirely.

One safety note in passing: since v2.1.216, saves check the target for symlinks and refuse to write through one at the project scope, so a save can't silently land outside the repo. A dotfiles-managed ~/.claude still works; only the file itself can't be a link.

OBJECTION

“Saved workflows are like saved prompts.” A saved prompt is a suggestion the model reinterprets every time. A saved workflow is a program: same orchestration, same verification gates, same exit conditions on every run, parameterized only through args. The difference shows up on the tenth run, when the prompt would have drifted and the script hasn't.

Designing for other callers

The moment a workflow has a name, it has users, and users interact with exactly three surfaces: args going in, a structured result coming out, and the whenToUse line that tells them whether to invoke it at all. Design all three like the API they are.

Start with args. Few parameters, defaults for every one of them, and validation at the top that fails loudly before any agent spends money. Compare:

// Before: works on my machine
const files = args
const audits = await pipeline(files, ...)
// After: works on her machine
const dir = args?.dir ?? 'src/routes/'
const severityFloor = args?.severityFloor ?? 'low'
if (args?.dir && typeof args.dir !== 'string') {
  throw new Error(`dir must be a path string, got ${typeof args.dir}`)
}

The before version costs nothing until a caller passes a bare string where the script expected an array, and then it costs thirty agents and a confusing null. The after version costs four lines. Error messages are part of the contract too: “dir must be a path string” tells a caller what to fix; a null-pointer trace tells them to find you on the beach.

whenToUse is your discovery surface. It renders in the workflow list, so write the sentence that lets a teammate pick the right tool: “Run before release when route handlers changed” beats “Audits stuff.” Naming follows the same logic. Verb-noun, specific over cute: audit-routes, check-dep-upgrades, not security-thing or bob-special.

Returns carry part two's rule to a second audience. Structured summaries, never transcripts, because a caller who builds on your workflow needs fields to branch on, and part five composes workflows through exactly this contract. What you design here is what conductors consume there.

On documentation: the script is short enough to read, so the contract, the whenToUse line, and a two-line header comment usually cover it. Write a README when the args have real complexity, and not before.

Review it like code, because it is

A workflow spends money at runtime, and its failure modes are quiet: a loop that never converges, a cap nobody logged, a schema that lets junk through. The reviewer is the last cheap gate before those failures bill by the agent. Review accordingly, with a checklist tied to the failure it prevents.

CHECKWHAT GOES WRONG OTHERWISE
Every loop has a named exit conditionThe 1,000-agent backstop becomes your terminator
Open-ended work has a budget guardAn unbudgeted loop sprints at the cap
filter(Boolean) after every fan-outOne dead agent crashes the merge, thirty agents in
Schemas: required fields, enums for branching values“High”, “high”, and “HIGH” in the same report
No silent caps: every top-N and sample loggedPartial coverage reads as full coverage
Explicit phase labels inside concurrent stagesProgress groups race and the run is unreadable
Prompts ask for data, not essaysThe script ends up parsing prose
Args validated before the first agent callFailures cost agents instead of milliseconds

Each row is a part-three pattern wearing a reviewer's hat, and the vocabulary is deliberate: if a row surprises you, part three is the reading.

Review catches structure. It doesn't catch a prompt that underperforms on your codebase, so a reviewed workflow still gets its first run on a small slice with the token view open. Two gates, different failure classes, both cheap.

Two habits round out ownership. Pin the Claude Code version the workflow was written against (a comment in meta is enough) and re-test when the changelog touches workflow behavior; this feature moves fast and the changelog is the watch-list. And give every saved workflow a code owner like any other file. Orphaned automation doesn't crash, it quietly goes stale, and stale automation that spends tokens is the worst kind. The telemetry review in part six is where staleness gets caught, but only if someone's name is on the file.

OBJECTION

“Personal scope is fine for team processes.” Personal workflows skip review, skip discovery, and vanish when the laptop does. Scope is a statement about who may depend on the thing. If a process matters to the team, the workflow belongs where the team can read it, review it, and fix it at 2 a.m. without calling you.

Plugins: shipping beyond one repo

When three repos want the same workflow, copying the file three times works until the first bug fix. The org-scale answer is a plugin.

Workflows are a first-class plugin component. Put the scripts in a workflows/ directory at the plugin root, peers with skills/, commands/, and agents/, same file format, nothing about the script changes. Invocation picks up a namespace: a plugin called acme-tools containing a script whose meta name is release-audit runs as /acme-tools:release-audit. Collisions across plugins are impossible by construction.

Now the gotcha, because you will hit it. The workflows field in plugin.json replaces the default directory scan instead of adding to it:

{ "workflows": "./custom/workflows/" }

Ship that, and everything in your ./workflows/ directory silently stops shipping. No error, no warning, just workflows missing after install. The fix is listing both:

{ "workflows": ["./workflows/", "./custom/workflows/"] }

Skills behave the opposite way (custom paths add to the default), which is exactly why this bites people. If workflows vanish after a manifest edit, check the array before you file the issue.

Distribution is the payoff. Plugins travel through marketplaces, so a platform team ships the org's workflow library the way it ships lint rules, and updates arrive as plugin version bumps. That gives the plugin path something the repo path lacks: release discipline. A repo workflow changes whenever its file changes; a plugin workflow changes when someone cuts a release, with a version number your org can pin.

The decision between them takes one paragraph. Repo when the workflow is coupled to one codebase's layout and vocabulary. Plugin when three repos want it, when a platform team owns it, or when versioned releases matter. Migration later is copying a file, so choose for today and don't agonize.

OBJECTION

“The manifest adds paths.” It replaces them. This is the most common plugin-workflow bug report, the symptom is silence (workflows just absent after install), and the fix is one line in the array. Check it first.

Case study: acme-tools

A sketch, composite but realistic, of what six weeks of this looks like.

A platform team ships a plugin with three workflows. /acme-tools:release-audit is the audit from part three, generalized: dir and severityFloor args, verify phase, ranked report. /acme-tools:dep-upgrade-check fans out over changed lockfile entries and verifies claimed breaking changes against real changelogs. /acme-tools:incident-sweep is a loop-until-dry search for a pattern across services, built the week after a postmortem asked “where else does this exist?”

Adoption took an announcement with the three whenToUse lines, one lunch demo of the /workflows view, and args contracts in the plugin README. Support load after week two was near zero, and the reason is boring: error messages told callers what to fix, so callers fixed things.

Iteration came from part six's phase-four telemetry review. dep-upgrade-check grew a budget guard after one 300-agent surprise (a lockfile diff nobody expected to be that large). release-audit's severity enum gained a level because reviewers kept asking for one between medium and high. Both changes were one-file PRs with a reviewer, which is the entire point.

The bar this team cleared is the one part six sets for rollout phase two: a teammate who didn't write a workflow runs it successfully from its name alone. Contracts, review, and a namespace are what clearing it takes.

What to do next

Promote one workflow this week. Take the audit workflow you built in parts two and three, run the review checklist on it, write the args contract and the whenToUse line, and save it to project scope. Then watch a teammate run it without your help. Every question they ask is a contract defect; fix the contract, not the teammate.

Part five is where the library pays compound interest: composing these named workflows under higher-level scripts, with the contracts you just designed as the interface. For leaders mapping this to the org, this post is phase two of part six's rollout playbook.

← Part 3: Pipelines, Barriers, and Verification: Orchestration Patterns That Actually Converge
Part 5: Conductors: Composing Predefined Workflows with Generated Orchestration →

Written against Claude Code v2.1.239. The changelog tracks what's moved since.