aboutsummaryrefslogtreecommitdiff
path: root/skills/command-composition-rules.json
blob: 4120cbdf54da4e07d599650de27fdcb1bd3bb288 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "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",
  "created": "2026-10-23T16:27:07.000Z",
  "updated": "2026-10-23T16:27:07.000Z",
  "content": "---\nname: command-composition-rules\ndescription: The terminal has no shell: what is refused, what works, and how to verify a result that looks plausible\ntags: [terminal, shell, composition, safety, agent]\ncategory: system\n---\n## The rule\n\nThis terminal does not spawn a shell. The line is split into argv and handed to **one** command\nhandler. \\`;\\`, \\`&&\\`, \\`||\\` and redirection are not interpreted. Since v4.6.58 they are refused:\n\n    wc -l file && grep -c pattern file\n    -> warning: No shell here \u2014 \\`&&\\` is not interpreted. Run one command per line.\n\n## Why the refusal exists\n\nBefore v4.6.58 \\`&&\\` failed silently: the right-hand tokens were appended to the left command's\nargv, unknown tokens were dropped, and valid flags were absorbed.\n\n    wc -l f && grep -c x f   ->  \"42 42\"    (grep's -c became a wc flag)\n    wc -l f && grep -n x f   ->  \"42\"       (-n is not a wc flag, so it was dropped)\n    wc -c a b                ->  \"34138 a\"  (second path dropped, exit code 0)\n\nThe worst case was an empty pattern: \\`grep \"\" file\\` matches every line and returns a plausible\ncount. A clean exit code is not evidence that the command did what you asked.\n\n## What works\n\n- One command per call. Two files means two calls, unless the command documents multiple paths.\n- \\`wc\\` accepts multiple paths since v4.6.58: \\`wc -c a b\\` prints one line per file plus \\`total\\`.\n- A single \\`cmd | cmd\\` pipeline works in the **interactive** terminal. It does **not** work through\n  the AI \\`run_command\\` tool, which bypasses the pipe splitter \u2014 there \\`|\\` is refused too.\n- Operators inside quotes are untouched: \\`echo \"a && b\"\\` prints \\`a && b\\`.\n\n## Verifying your own result\n\n- Ask for two things, count that you got two lines back.\n- A suspiciously round or empty count means a broken probe, not a clean system.\n- \\`ls\\` prints text since v4.6.57 (it used to render chips only, so captured output was empty);\n  \\`list_files\\` remains the reliable enumerator.\n- An invalid \\`grep\\` pattern now returns \\`grep: invalid pattern: \u2026\\` and does not enter the error\n  ledger \u2014 if you see a pattern exception, something regressed.\n\n## Pitfalls\n\n- A refusal is not a failed command: nothing ran. Split it and run both halves.\n- \\`;\\` used to fail loudly (\"No such file: x;\"). That loudness is gone in favour of an explicit\n  refusal \u2014 do not \"fix\" a refusal by re-joining the commands differently.\n- \\`>\\` is handled by \\`echo\\` only (\\`echo hi > file\\`). It is not a general redirect.\n\n## History\n\nReplaces \\`terminal-commands-silently-failing\\` and \\`command-composition-rules\\`, merged 2026-10-23\nafter the behaviour itself was fixed in v4.6.58."
}