Skip to content

The shell provider cannot replace the current process: no execve action #802

Open

Description

@David-Noble-at-work

Part of #799.

There is no way for a Star program to become another process. A launcher that ends by handing the
terminal to tmux attach — the bash idiom exec tmux attach-session -t "$id" — cannot express
that step. shell.exec forks, waits, and captures both streams into buffers, so the successor gets
no terminal to draw on and the caller sits behind it.

The contract

execve(2): As the execve() function overlays the current process image with a new process image, the
successful call has no process to return to. If execve() does return to the calling process, an
error has occurred.

Go models it exactly — func Exec(argv0 string, argv []string, envv []string) (err error). No
bool, no result: a nil return is unreachable, and returning at all is the failure signal.

Contrast the neighbours: fork returns twice, execve zero times on success, ForkExec once
in the parent. shell.exec today is the third. This is the second. They are different syscalls
with different arities, not one syscall with a flag — which is the argument for a separate action
rather than an option.

Two constraints

Admissible only in the ambient immediate starlarkbridge.Runtime. In a graph it removes the
executor's entire contract: no receipt to stamp (the recorder is gone), no compensator to unwind (no
process to unwind in), no context left to cancel (the runtime has been replaced). Immediate dispatch
promises none of those — a nil-Graph activation has no unit to stamp and no downstream node
expecting a value — so divergence takes nothing away. The Graph == nil seam already separates the
two dispatch stacks and is where this is enforced.

Nothing deferred survives. execve runs no userspace cleanup: no defer, no atexit, no
finalizers. The kernel closes close-on-exec descriptors but never flushes them, so a pending
bufio.Writer loses bytes while its fd closes cleanly — silent truncation. Everything buffered must
flush first, and that cannot be the action's job alone: ui.* output and telemetry belong to other
components. Either the runtime grows an explicit pre-divergence flush, or the action documents a
precondition and accepts silent loss when callers miss it.

The same asymmetry is why it works: Go marks its own descriptors close-on-exec while 0, 1 and 2 are
not marked, so the terminal passes through and the successor inherits a real tty.

In dry-run it does not diverge

Runner's dry-run contract — narrate, skip execution — is meaningful here: in dry-run the action
narrates what it would become and returns normally. Divergence is a property of the action in a live
run
, not of the action. That does not soften the admissibility rule: a plan that previews correctly
and vaporises the executor on apply is worse than one that fails both times.

Blocked on the taxonomy work — the action interfaces have no signature for this.

Plan and design documents

Kind Document
Plan not yet — the plan arrives when the branch does
Design draft in hand, uncommitted; lands with the first worktree

Epic: #815 · Feature: #799

Activity

  1. added
    enhancementNew feature or request
    Thread:Ops:PortableToolingTooling axis: the process commands work the same on every platform, shipped by the base layer
    taskImplementation work item
    Epic:ProvidersThe provider surface: one feature per provider
    and removed
    enhancementNew feature or request
    on Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area:ArchitectureSystem architecture and designEpic:ProvidersThe provider surface: one feature per providerThread:Ops:PortableToolingTooling axis: the process commands work the same on every platform, shipped by the base layertaskImplementation work item

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions