Summary
The tmux command-string construction in pkg/runtime/common.go:379-386 uses an incomplete character set when deciding whether to shell-quote harness args. Tokens containing backticks, parens, braces, brackets, backslashes, !, or glob characters (*, ?, ~) fall through unquoted and get re-interpreted by the shell when the tmux new-session string is parsed. This manifests as silent prompt mangling when task strings contain ordinary markdown.
A correct fix already exists elsewhere in the repo — pkg/runtime/k8s_runtime.go:2016-2020 uses POSIX single-quote escaping (' → '"'"') for the K8s su -c path, and a comment at k8s_runtime.go:791 notes that this was done to "avoid shell quoting issues with the tmux command string." That fix never propagated to the Docker, Podman, or Apple Container runtimes, which all share the pkg/runtime/common.go code path.
Impact
Observed in a real dispatch: a task prompt containing backtick-wrapped file paths (standard markdown — `apps/backend/src/templates/notificationTemplates.ts`) triggered zsh inside the tmux session to execute each backtick as command substitution:
zsh:1: permission denied: apps/backend/src/templates/notificationTemplates.ts
zsh:1: command not found: 0xD32F2F
The harness received a prompt with the backtick-wrapped sections replaced by error strings / empty strings. The agent silently executed a mangled instruction — no error surfaced to the dispatcher, the run just produced confused output. This is the worst failure mode for autonomous systems: looks like it worked, didn't actually work.
The same class of failure is reachable with any task string containing parens (clarifications), $ (prices, env refs — currently caught), globs (file patterns), or embedded shell metacharacters.
Current code
pkg/runtime/common.go:379-386:
for _, a := range harnessArgs {
if strings.ContainsAny(a, " \t\n\"'$") {
quotedArgs = append(quotedArgs, fmt.Sprintf("%q", a))
} else {
quotedArgs = append(quotedArgs, a)
}
}
Two problems with this:
- Character set is incomplete.
strings.ContainsAny misses backticks, (, ), {, }, [, ], \, !, *, ?, ~. Any of those in a bare token survives un-quoted and gets shell-interpreted.
%q is Go's syntax, not shell's. Go's %q uses Go escape conventions (e.g., \n, \t, \xNN) that are not identical to POSIX shell quoting. Double-quoted strings in shell still evaluate $, backticks, and \ — so even when quoting is applied, command substitution inside the token isn't fully neutralized.
Proposed fix
Mirror the K8s path's POSIX single-quote escaping. Always single-quote (don't gate on character-set detection), and handle embedded single-quotes with the standard '"'"' pattern:
func posixShellQuote(s string) string {
return "'" + strings.ReplaceAll(s, "'", `'"'"'`) + "'"
}
for _, a := range harnessArgs {
quotedArgs = append(quotedArgs, posixShellQuote(a))
}
POSIX single quotes disable all shell interpretation inside the token — no command substitution, no variable expansion, no glob, no backslash escapes. This is the standard idiom and it's already what k8s_runtime.go:2019 does.
Benefits:
- Covers every metacharacter, including ones not yet considered.
- Matches the K8s path — one quoting implementation across all runtimes.
- Cheap: always-quote adds a few bytes to the tmux command string and costs nothing at runtime.
- No behavior change for well-formed prompts; only previously-broken prompts start working correctly.
Acceptance
pkg/runtime/common.go:379-386 uses POSIX single-quote escaping for harness args.
- A unit test in
pkg/runtime/common_test.go exercises task strings containing: backticks, $foo, $(whoami), parens, braces, globs (*.ts), embedded single quotes, embedded double quotes, newlines, and a markdown-ish realistic example. The test asserts the harness receives the exact bytes passed in (no shell re-interpretation).
- Behavior verified against all three runtimes (Docker, Podman, Apple Container) — quoting is runtime-agnostic but worth running the integration path in each.
- K8s path already uses the correct quoting; no change needed there.
Out of scope
- Any redesign of how prompts are passed (positional vs. file vs. stdin). This bug-fix issue is purely about correctness of the existing inline path.
- Escape-char handling inside the harness itself (Claude/Gemini/Codex prompt parsing) — that's downstream of this.
Context
Branch main, commit 22dfe46a. No prior issue or PR touches this escaping path; the comment at k8s_runtime.go:791 is the closest thing to acknowledgment in the codebase.
Summary
The tmux command-string construction in
pkg/runtime/common.go:379-386uses an incomplete character set when deciding whether to shell-quote harness args. Tokens containing backticks, parens, braces, brackets, backslashes,!, or glob characters (*,?,~) fall through unquoted and get re-interpreted by the shell when the tmuxnew-sessionstring is parsed. This manifests as silent prompt mangling when task strings contain ordinary markdown.A correct fix already exists elsewhere in the repo —
pkg/runtime/k8s_runtime.go:2016-2020uses POSIX single-quote escaping ('→'"'"') for the K8ssu -cpath, and a comment atk8s_runtime.go:791notes that this was done to "avoid shell quoting issues with the tmux command string." That fix never propagated to the Docker, Podman, or Apple Container runtimes, which all share thepkg/runtime/common.gocode path.Impact
Observed in a real dispatch: a task prompt containing backtick-wrapped file paths (standard markdown —
`apps/backend/src/templates/notificationTemplates.ts`) triggered zsh inside the tmux session to execute each backtick as command substitution:The harness received a prompt with the backtick-wrapped sections replaced by error strings / empty strings. The agent silently executed a mangled instruction — no error surfaced to the dispatcher, the run just produced confused output. This is the worst failure mode for autonomous systems: looks like it worked, didn't actually work.
The same class of failure is reachable with any task string containing parens (clarifications),
$(prices, env refs — currently caught), globs (file patterns), or embedded shell metacharacters.Current code
pkg/runtime/common.go:379-386:Two problems with this:
strings.ContainsAnymisses backticks,(,),{,},[,],\,!,*,?,~. Any of those in a bare token survives un-quoted and gets shell-interpreted.%qis Go's syntax, not shell's. Go's%quses Go escape conventions (e.g.,\n,\t,\xNN) that are not identical to POSIX shell quoting. Double-quoted strings in shell still evaluate$, backticks, and\— so even when quoting is applied, command substitution inside the token isn't fully neutralized.Proposed fix
Mirror the K8s path's POSIX single-quote escaping. Always single-quote (don't gate on character-set detection), and handle embedded single-quotes with the standard
'"'"'pattern:POSIX single quotes disable all shell interpretation inside the token — no command substitution, no variable expansion, no glob, no backslash escapes. This is the standard idiom and it's already what
k8s_runtime.go:2019does.Benefits:
Acceptance
pkg/runtime/common.go:379-386uses POSIX single-quote escaping for harness args.pkg/runtime/common_test.goexercises task strings containing: backticks,$foo,$(whoami), parens, braces, globs (*.ts), embedded single quotes, embedded double quotes, newlines, and a markdown-ish realistic example. The test asserts the harness receives the exact bytes passed in (no shell re-interpretation).Out of scope
Context
Branch
main, commit22dfe46a. No prior issue or PR touches this escaping path; the comment atk8s_runtime.go:791is the closest thing to acknowledgment in the codebase.