Skip to content

Refactor port spawning: use direct spawn_executable instead of sh -c #3

Description

@ppsplus-bradh

Problem

Adapter.Port.open_cli_port/4 spawns the CLI binary by invoking /bin/sh -c with a concatenated command string. This requires shell escaping every component — environment variable values, the executable path, all CLI arguments, and the working directory — via a hand-rolled shell_escape/1 function.

This approach has several drawbacks:

  1. Shell escaping is inherently fragile. The original implementation used an allowlist of 12 dangerous characters and missed !, #, <, >, ?, [, ], {, }, *, ~, tab (fixed in shell_escape/1 fails to quote strings with certain shell-special characters guess/claude_code#38/PR Fix shell_escape to quote all non-safe characters guess/claude_code#39 by inverting to a safelist). Even with the safelist fix, any escaping logic is a potential source of bugs.

  2. Unnecessary indirection. Erlang's Port.open/2 with :spawn_executable natively supports :args (list of strings), :env (list of {charlist, charlist} tuples), and :cd (charlist) — all without touching a shell. The arguments are passed directly to execvp, bypassing shell interpretation entirely.

  3. Extra process layer. The sh -c "exec ..." pattern spawns a shell process that then execs the CLI. Direct :spawn_executable avoids this.

Solution

Replace the sh -c invocation with direct Port.open({:spawn_executable, exe_path}, [{:args, args}, {:env, env_list}, {:cd, cwd}, ...]). This eliminates build_shell_command/4, shell_escape/1, and @shell_safe_pattern entirely.

Branch

refactor/direct-spawn-executable

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions