anomalyco / anomalyco/opencode
[FEATURE]: Add a structured argv execution mode to the existing shell tool
@nexxeln is already working on this.
Since Aug 21, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
- I have verified this feature I'm about to request hasn't been suggested before.
I searched the public issue index for exec_argv, literal stdin, and similar structured-execution terms and did not find an equivalent proposal. This was a targeted search, not a guarantee that no related discussion exists.
Summary
I propose extending the existing shell execution tool with a structured single-process mode, rather than adding a second agent-facing tool. The result would remain one execution system and one permission capability with two input modes:
- shell mode: the current command-string behavior, for shell grammar;
- process mode: one direct child process described by a program and argument vector (argv), without inserting the configured shell.
This keeps permissions, hooks, environment policy, timeout, cancellation, output handling, truncation, and user experience in one execution path. It also preserves the existing bash tool/permission identity for compatibility until any planned OpenCode 2.0 rename.
Motivation
The current tool exposes only { command, timeout?, workdir? }, parses the command string with Tree-sitter for permission resources, and executes it through the configured shell. That is necessary for pipelines and other shell constructs, but it makes every simple native command cross a quoting and parsing boundary even when no shell behavior is needed.
A structured mode would let OpenCode pass a program and arguments directly to its process runtime. This does not replace shell mode and is not a sandbox. It only avoids shell interpretation when the requested operation is one native process.
OpenCode already has relevant internal machinery: ChildProcess.make(program, args, options) is supported by packages/core/src/cross-spawn-spawner.ts. The feature could therefore reuse the existing execution lifecycle rather than create a parallel tool implementation.
Proposed input
Existing calls remain unchanged and imply shell mode:
{
"command": "git status",
"workdir": "/path/to/repository",
"timeout": 30000
}
A structured call could use an explicit discriminator:
{
"mode": "process",
"program": "git",
"args": [
"commit",
"-m",
"literal multiline message"
],
"stdin": "optional literal input",
"workdir": "/path/to/repository",
"timeout": 30000
}
Suggested schema rules:
- omitting
modepreserves the historicalcommandshape; mode: "process"requires a non-emptyprogramand anargsarray, which may be empty;argscontains strings directly, not a JSON-encoded array;commandcannot be combined withprogramorargs;- unknown modes, mixed shapes, invalid types, and unsupported combinations fail before permission evaluation or process creation;
- literal stdin is initially limited to process mode.
The exact discriminator and field names are open to maintainer preference.
Process-mode semantics
- Launch one direct child process through the runtime's structured
programplusargsAPI, without inserting the configured shell. - Pass spaces, quotes, newlines, empty arguments, and shell metacharacters as argument data rather than shell syntax.
- Keep shell mode for pipelines, redirects, expansions, compound commands, shell built-ins, and other shell grammar.
- An explicitly invoked interpreter such as
bash -c,python -c, ornode -eremains able to interpret code and must not be treated as intrinsically safe. - Executable lookup, scripts requiring an interpreter, and
.cmd/.batbehavior must be defined per supported platform. Unsupported executable forms should fail clearly rather than silently changing modes. - Platform tests should verify the runtime's documented behavior for spaces, quotes, newlines, empty arguments, and metacharacters. The claim is direct structured invocation without an inserted shell, not identical operating-system argv internals on every platform.
“One process” here means one direct child; the child may create descendants.
Permission and audit semantics
Both modes should consult the same execution permission capability. This is necessary but not sufficient: changing representation must not accidentally broaden an existing allow, ask, or deny rule.
For process mode:
- permission evaluation should receive the structured program and argument tokens, without reparsing a shell command string;
- the implementation should define a deterministic canonical representation for compatibility with existing string rules;
- the human-readable preview must be derived from, but never reused as, the execution source;
- raw structured values used internally for matching must be distinguished from escaped display metadata and persisted audit data;
- compatibility should be tested with representative rules and arguments containing spaces, quotes, empty strings, newlines, and metacharacters;
- if a request cannot be represented or classified unambiguously, it must not receive broader permission by default.
Exact tokens improve fidelity but do not reveal all effects. Path and external_directory checks may consume exact token boundaries where applicable, but argv alone cannot identify every path, wrapper, interpreter payload, network effect, or filesystem mutation. This mode must not be presented as a sandbox or authorization boundary.
Literal stdin
stdin should be explicit and bounded:
- when absent, the child receives closed/ignored stdin and never inherits terminal user interface (TUI) input;
- when present, it is a bounded UTF-8 string; binary stdin is outside this initial proposal;
- the runtime writes all bytes while handling backpressure and write errors, then closes stdin immediately after the final byte, and only then waits for process completion;
- empty stdin is distinct at the schema level from absent stdin, but both produce an observable end-of-file rather than an inherited or dangling stream;
- timeout, cancellation, early child exit, or broken pipe must close or destroy the input stream cleanly;
- literal stdin should not be copied into permission prompts, routine logs, or persisted audit data by default. Presence and byte length can be shown; any preview/redaction policy should be explicit.
A regression test should include a child that reads until end-of-file.
Shared lifecycle
Process mode should reuse, or reach observable parity with, the existing shell path for:
- working-directory validation;
shell.envand environment construction;- plugin hooks;
- execution permission and
external_directorygates; - timeout and cancellation;
- process-group cleanup where supported;
- stdout/stderr collection, truncation, exit status, and error reporting;
- tool metadata and UI lifecycle.
Each item should have focused compatibility coverage rather than being assumed from sharing a tool name.
Structured per-call environment overrides could be useful because they avoid VAR=value command wrappers, but I would leave them outside the initial scope until secret visibility and permission semantics are defined.
Non-goals
- Replacing shell mode.
- Supporting shell grammar inside process mode.
- Creating a sandbox or proving a program's effects.
- Automatically treating direct execution as safe.
- Adding structured pipelines in the initial change.
- Adding per-call environment overrides before their permission and secret-handling contract is clear.
Related reports
The proposal would avoid some shell-specific failure paths for process-mode calls, without claiming to resolve every report:
- #42402: Windows PowerShell 5.1 can silently strip or split native arguments and still return success.
- #39884: PowerShell escaping can corrupt verbatim Markdown and JSON.
- #39931, #42436, and #42184: command parsing and normalization materially affect permission behavior and fail-closed design.
- #41612 and #42773: inherited or dangling stdin can interfere with the TUI or leave children waiting indefinitely.
- #38376: direct executable lookup has Windows-specific
.cmdconstraints that the design must address explicitly. - #43775: native no-shell spawning is already being used for a narrower internal Windows Git path, although not as an agent-facing mode.
Relevant current sources:
Acceptance criteria
- Existing command-string calls remain compatible.
- Process mode has an unambiguous validated schema and launches through the structured runtime API without inserting the configured shell.
- Argument-boundary behavior is tested on every supported platform, including the platform-specific executable resolution contract.
- Both modes evaluate the same execution permission capability, with deterministic representation and fail-closed behavior for ambiguous cases.
- Existing representative permission rules are regression-tested against process mode.
- stdin is never inherited, is closed by default, and explicit UTF-8 stdin is fully written and closed before waiting for process completion.
- Both modes have verified parity for the shared lifecycle items listed above.
- User-facing previews are not execution sources, and stdin is not exposed or persisted by default.
- Documentation tells the model to prefer process mode for a single native program and shell mode only when shell grammar is required.
- The documentation explicitly states that neither mode is a sandbox and that interpreters and wrappers remain powerful.
Open design questions
- Is the input best represented by an explicit
mode, or by another unambiguous discriminated shape? - What canonical process representation should preserve useful compatibility with existing string-based
bashpermission rules without making ambiguous requests broader? - Should the UI show both an escaped command preview and a structured program/argument view?
- What executable forms are supported directly on Windows, especially
.cmdand.bat? - Should bounded literal stdin later become available to shell mode as well?
I have a working local custom-tool prototype on Linux that exercises direct argv, bounded literal stdin, timeout/output limits, and explicit permission requests. It is not integrated with the shell tool, does not validate the shared-permission design above, and has not been established as cross-platform. If this direction is useful, I would be happy to prepare an implementation for review.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.