aboutsummaryrefslogtreecommitdiff
path: root/skills/command-composition-rules.md
blob: aa218e73486d15b208220481647ed0c9aab88214 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
---
name: command-composition-rules
description: The terminal has no shell: what is refused, what works, and how to verify a result that looks plausible
tags: [terminal, shell, composition, safety, agent]
category: system
---
## The rule

This terminal does not spawn a shell. The line is split into argv and handed to **one** command
handler. \`;\`, \`&&\`, \`||\` and redirection are not interpreted. Since v4.6.58 they are refused:

    wc -l file && grep -c pattern file
    -> warning: No shell here — \`&&\` is not interpreted. Run one command per line.

## Why the refusal exists

Before v4.6.58 \`&&\` failed silently: the right-hand tokens were appended to the left command's
argv, unknown tokens were dropped, and valid flags were absorbed.

    wc -l f && grep -c x f   ->  "42 42"    (grep's -c became a wc flag)
    wc -l f && grep -n x f   ->  "42"       (-n is not a wc flag, so it was dropped)
    wc -c a b                ->  "34138 a"  (second path dropped, exit code 0)

The worst case was an empty pattern: \`grep "" file\` matches every line and returns a plausible
count. A clean exit code is not evidence that the command did what you asked.

## What works

- One command per call. Two files means two calls, unless the command documents multiple paths.
- \`wc\` accepts multiple paths since v4.6.58: \`wc -c a b\` prints one line per file plus \`total\`.
- A single \`cmd | cmd\` pipeline works in the **interactive** terminal. It does **not** work through
  the AI \`run_command\` tool, which bypasses the pipe splitter — there \`|\` is refused too.
- Operators inside quotes are untouched: \`echo "a && b"\` prints \`a && b\`.

## Verifying your own result

- Ask for two things, count that you got two lines back.
- A suspiciously round or empty count means a broken probe, not a clean system.
- \`ls\` prints text since v4.6.57 (it used to render chips only, so captured output was empty);
  \`list_files\` remains the reliable enumerator.
- An invalid \`grep\` pattern now returns \`grep: invalid pattern: …\` and does not enter the error
  ledger — if you see a pattern exception, something regressed.

## Pitfalls

- A refusal is not a failed command: nothing ran. Split it and run both halves.
- \`;\` used to fail loudly ("No such file: x;"). That loudness is gone in favour of an explicit
  refusal — do not "fix" a refusal by re-joining the commands differently.
- \`>\` is handled by \`echo\` only (\`echo hi > file\`). It is not a general redirect.

## History

Replaces \`terminal-commands-silently-failing\` and \`command-composition-rules\`, merged 2026-10-23
after the behaviour itself was fixed in v4.6.58.