Skip to content

Replace implicit shell command strings with argv execution in carve-changesets #71

Description

@shaug

Goal

Remove implicit shell parsing from carve-changesets validation and database-comparison commands while preserving the ability to run ordinary argv commands and explicitly approved shell programs.

Current evidence

The current implementation passes caller- or plan-provided strings to subprocess.run(..., shell=True) in:

  • scripts/preflight.py for source-branch validation;
  • scripts/chain.py after each reconstructed changeset; and
  • scripts/db_compare.py for source and chain schema commands.

skills.sh currently reports these as command-execution/anomaly findings. The skill contract already requires separate approval, but the helper still gives every approved string implicit shell metacharacter semantics.

Required design

Introduce one canonical command specification and executor for the skill:

  • the safe/default representation is a non-empty JSON-compatible argv array of strings;
  • the executor calls subprocess.run(argv, shell=False, ...) without string interpolation;
  • plan schema and CLI surfaces use argv forms for test, source-schema, and chain-schema commands;
  • callers that genuinely require pipes, redirects, environment expansion, or compound shell syntax express the shell explicitly in argv, for example ['sh', '-lc', '...']; and
  • discovered repository commands remain display-only proposals and are never converted into an approved argv specification automatically.

Provide a documented compatibility transition for existing string-based --test-cmd, --source-cmd, --chain-cmd, and plan.test_command inputs. Do not silently reinterpret a legacy string with shlex.split, because that changes quoting and shell semantics. A legacy string may either fail with a precise migration message or require an explicit shell acknowledgement during a bounded migration window; in either case there must be no implicit shell=True execution.

Failure and safety behavior

  • Reject empty argv, non-string elements, NUL bytes, malformed JSON, and unknown command representations before changing branches.
  • Print a shell-escaped display form for operator review without reusing that display string for execution.
  • Preserve checkout restoration, temporary-branch deletion, dirty-tree detection, interruption precedence, and command exit status.
  • Never derive an argv command from AGENTS.md, package metadata, scripts, comments, or a plan without the existing separate approval boundary.

Expected change surface

  • skills/carve-changesets/scripts/ command executor plus preflight, chain validation, database comparison, and CLI parsing
  • plan schema, validation, examples, and migration diagnostics
  • focused unit/integration/eval fixtures
  • SKILL.md, CLI/SPEC references, README, and CHANGELOG where behavior is user-facing

Acceptance criteria

  • No subprocess.run(..., shell=True) remains in production carve-changesets command execution.
  • Ordinary commands execute from approved argv arrays with exact argument boundaries.
  • Shell behavior remains possible only by explicitly selecting a shell executable and its arguments.
  • Pipes, redirects, quoting, environment expansion, and compound commands have documented explicit-shell examples.
  • Legacy string inputs are never silently split or implicitly executed; their migration behavior is deterministic and tested.
  • Malformed or empty command specs fail before branch mutation.
  • Commands discovered from repository content remain proposals and cannot auto-execute.
  • Existing branch restoration, clean-tree, equivalence, and failure semantics remain covered.
  • just format, just lint, and just test pass.

Non-goals

  • Remove arbitrary repository validation capability.
  • Allowlist specific build tools or languages.
  • Infer whether a command is safe from its spelling.
  • Hide command execution from security scanners.
  • Redesign unrelated git, GitHub, decomposition, or propagation mechanics.

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