runner: launch bare, and run shell functions as targets - #19
Conversation
cli-capture required a target on the command line and resolved it through exec.LookPath, so two things were impossible: starting the tool and deciding what to run afterwards, and targeting anything that is not a PATH binary. A zsh function like `claude-as` failed with a bare "executable file not found". Two ways in now: - No command at all launches an interactive $SHELL as the target. ProxyEnv already injects into whatever is launched, so every command run inside that shell is captured. - -shell wraps a named target in `$SHELL -ic`, which loads the rc file first, so aliases, functions, and rc-only PATH entries resolve. Arguments are single-quoted so they stay literal, but argv[0] is left bare when it matches a narrow metacharacter-free charset: in zsh, quoting any part of a word suppresses alias expansion, so a quoted name would still find a function while silently missing a user's alias. Start now pre-resolves argv[0] and returns ErrTargetNotFound, letting the command layer name the remedy — -shell for a target the user named, a broken $SHELL for a derived one. Also folds the duplicate POSIX quoting helper in internal/export into a shared internal/shellquote so the escaping rule has one home. Session-Name: bare-launch-shell-target Session-Id: 011b334d-55e0-41b7-8439-e0ffee421aff Model: claude-opus-5 Harness: claude-code 2.1.220 Machine: cf6e768835c7 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The README documented `-leader <key>` with a `ctrl+space` example, but main.go never defined the flag — passing it exited 2 with "flag provided but not defined", so the only way to move the leader was a config file. Two halves of the same gap: - Define -leader and let it win over the config file, mirroring how -theme already overrides cfg.Theme.Base. - Accept "ctrl+space" as a leader name. Terminals send NUL for it, the same byte as Ctrl+@, so bubbletea reports it as "ctrl+@" and the ctrl+a..ctrl+z scan never produced it. It is a common leader precisely because no target app claims it, and the README already promised it worked. Session-Name: bare-launch-shell-target Session-Id: 011b334d-55e0-41b7-8439-e0ffee421aff Model: claude-opus-5 Harness: claude-code 2.1.220 Machine: cf6e768835c7 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Follow-up commit
|
citizen-123
left a comment
There was a problem hiding this comment.
Two changes are required before merge:
-
[P1 privacy] cmd/cli-capture/main.go:229 logs the complete resolved argv. Command lines commonly contain bearer tokens, API keys, passwords, request bodies, and sensitive header values. Under -shell this persists the entire reconstructed shell command. This is new durable disclosure unrelated to capture output and also occurs when target startup later fails. Do not log argument values; log only a non-sensitive executable/shell identifier, or make detailed argv logging explicit opt-in with redaction.
-
[P2 correctness] internal/runner/runner.go:126-128 maps every exec.LookPath error to ErrTargetNotFound. LookPath can fail because an explicit target exists but is not executable (EACCES), among other non-ENOENT errors. The CLI then incorrectly says it was not found in PATH and recommends -shell, hiding the actionable permission error. Return ErrTargetNotFound only for an actual not-found condition and preserve other lookup errors, with a regression test for a non-executable target.
Verification note: go test ./... and go vet ./... pass at 5fa9fa3; these edge cases are not covered by the current tests.
cli-capturerequired a target on the command line and resolved it throughexec.LookPath, so two things were impossible: starting the tool and decidingwhat to run afterwards, and targeting anything that is not a
PATHbinary. Azsh function like
claude-asfailed with a bare "executable file not found".Two ways in
$SHELLas the target.ProxyEnvalready injects into whatever is launched, so every command runinside that shell is captured.
-shellwraps a named target in$SHELL -ic, which loads the rc filefirst, so aliases, functions, and rc-only
PATHentries resolve.Quoting
Arguments are single-quoted so they stay literal.
argv[0]is left bare whenit matches a narrow metacharacter-free charset — in zsh, quoting any part of a
word suppresses alias expansion, so a quoted name would still find a function
while silently missing a user's alias. A security pass confirmed no input after
--escapes the quoting; the boundary cases are pinned by tests.-shellis a deliberate waiver of the project's "pass args separately, neverthrough a shell" rule, with the reason recorded at the call site: resolving a
name only the user's shell knows requires the user's shell, the string is built
from the user's own argv, and the default path stays exec-direct.
Errors
Startnow pre-resolvesargv[0]and returnsErrTargetNotFound, letting thecommand layer name the remedy —
-shellfor a target the user named, a broken$SHELLfor a derived one.Before:
After:
Also
Folds the duplicate POSIX quoting helper in
internal/exportinto a sharedinternal/shellquote, so the escaping rule has one home.Verification
go build ./... && go vet ./... && go test ./...— 16 packages ok, gofmt clean.Error paths checked against the built binary. The interactive paths (bare launch
lands a shell; traffic from inside it is captured;
-shellruns the function)need a real TTY and were verified only as far as target launch.
Session-Name: bare-launch-shell-target
Session-Id: 011b334d-55e0-41b7-8439-e0ffee421aff
Model: claude-opus-5
Harness: claude-code 2.1.220
Machine: cf6e768835c7