slash-chain
You type /init && /initialize and expect the second to run too; the engine runs the first command alone and drops the rest without a word. This mod runs slash commands joined with && one after another, as a shell does: /tiny && /context runs /context once /tiny ended well.
What it does
The engine hands everything after the first command's name to that command as its arguments. The mod splits them at each
&& /<name>: the first command runs with its own arguments alone, and every/<name>after a&&becomes a step with its own arguments. A&&that no/<name>follows stays in the arguments, so/commit build && testkeeps its text.Each step waits for what it started before the next one runs:
- A command that hands the model a prompt (a skill or a prompt command) waits for its turn to end. A turn that ends with
answerruns the next step. A turn that ends witherror,refusaloraborted(Esc) stops the chain. A question the model asks with AskUserQuestion stays inside that turn, so the chain waits for the answer too. - A command that opens a focused pane (
/disk-janitor) waits until that pane closes. - A built-in dialog (
/cost,/model) holds its command until it closes. - Any other command is done when it returns.
- A command that hands the model a prompt (a skill or a prompt command) waits for its turn to end. A turn that ends with
A command that throws stops the chain.
Each step gets one transcript line, and the chain gets one last line:
1/2: /tiny 2/2: /context all 2 command(s) ran stopped after /tiny: its turn ended with aborted; not run: /contextA prompt or a new chain you type while a chain waits cancels it:
cancelled; not run: /context. The new chain starts from there. A single command you type (/cost) runs beside the waiting chain and does not cancel it, because the mod hooks only the commands whose arguments hold&& /<name>. The engine still writes this mod's name in front of every plugin command's output (task-poke+slash-chain: ...): it picks the names it writes by a hook'scommandmatcher alone, and a chain can start with any command, so this hook cannot name one (measured on 2.1.281 with two probe plugins: a hook with only anargsmatcher that never ran was named, a hook with acommandmatcher was not).
When a step fails in words
The engine gives a turn no exit code: a model that could not do a step, says so and ends its turn normally ends it with answer, and the next step would run. So a chained skill or prompt step that has steps after it gets one line after its text:
[slash-chain] This is step 1/2 of a chain; after it: /exit. If you could not do what this step asks, call the mcp__slash-chain__fail tool with the reason before you end your turn, and the steps after it do not run.
The mod declares the tool mcp__slash-chain__fail (reason) at the session's start and keeps it in the model's tool list, not behind ToolSearch, so the model can call it at once. A call stops the chain:
stopped after /fail: the model reported it failed: Writing /nonexistent-dir/CLAUDE.md failed: EROFS read-only file system, could not create /nonexistent-dir.; not run: /exit
Measured on 2.1.281: /fail && /exit, where /fail asked for a write that failed, called the tool, stopped before /exit, and the session stayed open; /ok && /context did not call it and ran both. A call while no chained step waits on the turn, from a subagent, or without a reason is refused. The last step of a chain and a command outside a chain get no line.
Command
/slash-chain the setting and the chain that runs, with what it waits for
/slash-chain stop cancels the chain that runs
/slash-chain on | off on by default; off leaves the command as the engine hands it
Install
claude plugin marketplace add KilimcininKorOglu/claude-code-mods
claude plugin install slash-chain@kilimcininkoroglu-mods
Function hooks are early access. Claude Code 2.1.288 and later load them by default, so there is nothing to switch on.
After installing
- Restart Claude Code.
What it can reach
Validated with claude plugin validate on Claude Code 2.1.283:
❯ ./register.ts hooks: session.start, tool.describe{tool=mcp__slash-chain__fail}, tool.call{tool=mcp__slash-chain__fail}, command.run{command=slash-chain}, command.run{args=/"(?:^|\\s)&&\\s*\\/[A-Za-z0-9_:.-]+(?=\\s|$)"/}, skill.prompt, ui.open, ui.close, turn.complete, prompt.submit
❯ ./register.ts calls: $.clock.after (via advance), $.command.register, $.command.run (via runStep), $.store.get (via readSettings), $.store.set (via setEnabled), $.tool.register, $.ui.log (via advance, cancel, runFirst, stop)
Reach L2, it drives Claude: it runs the slash commands you chained.
1. Reads: the arguments of every slash command, the name of each skill prompt, each pane's id and focus, and each main-loop turn's end reason. It reads no file, no prompt text and no answer; it reads the reason of a `fail` call.
2. Runs: each command after the first of a chain, once, with the arguments you typed for it; declares one tool, `mcp__slash-chain__fail`, at the session's start
3. Sends: to the model, the `fail` tool in its tool list and one line after the text of a chained skill or prompt step that has steps after it; nothing to the network
4. Persists: in $.store, the on/off setting
5. Hostile input: the chain comes from the text you typed; a step runs only a command the engine knows, with its own arguments, and a pasted text that holds `&& /<name>` in a command's arguments runs that command too
Limits
- The
failtool stands in the tool list of every session, also with no chain, because a tool cannot be taken back once declared. - The steps after the first run as a plugin (
origin.kind: 'plugin'), so a command that answers only the person refuses there./disk-janitor delete <path>is one. - A local command succeeds when it does not throw. The engine gives no other error signal, so a command that prints an error and returns counts as done.
- A skill or prompt command succeeds when its turn ends with
answerand the model did not callfail. A model that says it failed without calling the tool still counts as done: measured on 2.1.281 before the tool,/fail && /exit, where/failansweredI could not write CLAUDE.md., ran/exit, and the session closed. A turn you stop with Esc does stop the chain:/slow && /exitstopped withstopped after /slow: its turn ended with aborted; not run: /exit, and the session stayed open. /exitruns as a later step: the engine takes it from a plugin, and the session closes.- An unknown first command never reaches the mod: the engine answers
Unknown skill, and nothing after it runs. An unknown later command stops the chain with the engine's error:stopped after /x: it failed: $.command.run: no command named /x in this session. - Only
&&is read.||,;and|stay in the arguments. - A focused pane is recognized when it opens during its step. A pane a command opens later, from a timer, is not waited for.
- The chain was checked in an interactive session alone. A headless session (
claude -p) was not measured.
Development
make install # eslint, typescript-eslint, typescript
make lint # complexity limit 10, the build fails above it
make typecheck # needs .claude/types/ from /plugin-types
make validate
make test # claude plugin test