Stop a CLI workflow safely
Run a real, long-lived process through the experimental CLI, interrupt it with Ctrl+C, and verify that:
- the signal cancels the active AML evaluation;
- the local Sandbox terminates the complete process group and releases its lease;
- the CLI waits for cleanup, then exits with status
130forSIGINTor143forSIGTERM.
This example is intentionally credential-free. It uses localSandbox() to make process ownership visible, not to isolate untrusted code.
Prerequisites
Section titled “Prerequisites”- Node.js
>=26, repository dependencies, and a POSIX host withshandsleep; - the built repository CLI, or
@aml-jsx/cliinstalled beside@aml-jsx/sdk; - a terminal where you can send Ctrl+C;
- no model, Agent executable, credentials, or network access.
Building block: one explicit resource owner
Section titled “Building block: one explicit resource owner”This recipe places <Script /> inside a Sandbox so the example covers both process cancellation and Sandbox release. The local provider needs read-write because a host process cannot enforce read-only filesystem access. For a trusted command that does not need a Sandbox lifecycle, Script can execute directly on the host.
const LongRunningProcess = ( <Sandbox access="read-write" provider={localSandbox({ workspace: cwd() })}> <Script shell="sh"> {String.raw`sleep 300 &child=$!
if [ -n "$AML_PROCESS_SAFETY_PID_FILE" ]; then printf '%s\n' "$child" > "$AML_PROCESS_SAFETY_PID_FILE"fi
wait "$child"`} </Script> </Sandbox>)The inline shell starts a child process and waits for it. AML_PROCESS_SAFETY_PID_FILE is optional; when set, it records the child PID so cleanup can be checked independently. The Sandbox lease tracks the shell process, and local spawning creates a process group, so releasing the lease terminates the shell and its sleep descendant rather than abandoning the child.
The command does not install its own cleanup abstraction. Process lifetime belongs to the Sandbox provider, and application interruption belongs to the runner.
Main file: export the tree
Section titled “Main file: export the tree”The maintained file combines the imports and the two blocks above, then exports the AML value directly:
import { cwd } from "node:process"
import { localSandbox, Sandbox, Script } from "@aml-jsx/sdk"
// LongRunningProcess is the AML tree defined above.export default LongRunningProcessDo not construct AmlRuntime in this file. aml run must own the runtime for its signal controller to cover the Script, Sandbox release, diagnostics, and final exit code as one lifecycle.
Run and interrupt it
Section titled “Run and interrupt it”From the repository root:
npm run build --workspace=@aml-jsx/cliAML_PROCESS_SAFETY_PID_FILE=/tmp/aml-process-safety.pid \ node apps/cli/dist/index.js run ./examples/src/operations/cli-process-safety.tsx --traceWait for /tmp/aml-process-safety.pid to appear, then press Ctrl+C once. The CLI starts graceful cancellation and allows up to ten seconds for cleanup. A second interrupt explicitly stops waiting; it cannot guarantee JavaScript cleanup completed.
Check the conventional status and recorded child PID:
status=$?pid=$(cat /tmp/aml-process-safety.pid)printf 'exit=%s child=%s\n' "$status" "$pid"kill -0 "$pid"Expected: exit=130 ..., and kill -0 fails because the child no longer exists. If you send SIGTERM from another terminal, expect status 143.
What happens on the first signal
Section titled “What happens on the first signal”- The CLI’s process-signal owner aborts the signal passed to
runtime.evaluate()and starts its ten-second forced-exit deadline. - The active Script receives cancellation through the Sandbox command signal. The local provider terminates the tracked process group.
- AML unwinds the evaluation and releases the acquired Sandbox lease. Release is still called even though the Script did not complete normally.
- Only after evaluation cleanup settles does the CLI dispose its signal listeners and deadline, set the conventional exit code, and return control to Node.
Failure and safety boundaries
Section titled “Failure and safety boundaries”localSandbox()executes trusted host commands. Replace it with an appropriately configured container or remote Sandbox for hostile code.- The ten-second deadline bounds shutdown; it does not prove a remote provider reached a terminal state before forced exit.
- A second signal is an operator override. Use it only when stopping immediately is more important than completing cleanup.
- Application code using
AmlRuntimedirectly can useProcessSignalCancellation. Do not add a second signal owner aroundaml run. - Remove the optional PID file after the walkthrough; it is verification state, not an AML resource.