Skip to content

runner: launch bare, and run shell functions as targets - #19

Open
cameronsjo wants to merge 2 commits into
mainfrom
worktree-bare-launch-shell-target
Open

runner: launch bare, and run shell functions as targets#19
cameronsjo wants to merge 2 commits into
mainfrom
worktree-bare-launch-shell-target

Conversation

@cameronsjo

Copy link
Copy Markdown
Collaborator

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

  • 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.

Quoting

Arguments are single-quoted so they stay literal. 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. A security pass confirmed no input after
-- escapes the quoting; the boundary cases are pinned by tests.

-shell is a deliberate waiver of the project's "pass args separately, never
through 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

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.

Before:

start target: exec: "claude-as": executable file not found in $PATH

After:

start target: "claude-as": not found in $PATH — if it is a shell alias or
function, re-run with -shell (or launch a bare shell: cli-capture)

Also

Folds the duplicate POSIX quoting helper in internal/export into a shared
internal/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; -shell runs 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

cameronsjo and others added 2 commits August 3, 2026 08:28
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>
@cameronsjo

Copy link
Copy Markdown
Collaborator Author

Follow-up commit 5fa9fa3 closes an adjacent gap found during cleanup: the README documented a -leader <key> flag (with a ctrl+space example) that main.go never defined, so passing it exited 2 on an unknown flag.

  • -leader is now defined and wins over the config file, mirroring -theme.
  • ctrl+space is accepted as a leader name. Terminals send NUL for it — the same byte as Ctrl+@ — so bubbletea reports it as ctrl+@ and the ctrl+actrl+z scan never produced it.

cli-capture -leader ctrl+space -- claude-as alt now reaches the intended shell-alias error instead of a flag-parse failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant