Commit b873742
Eric Bower
·
2026-08-15 09:42:28 -0400 EDT
parent a5aaf30
docs: props
1 files changed,
+156,
-138
+156,
-138
| ... | ... | @@ -2,177 +2,195 @@ | |
| 2 | 2 | ||
| 3 | 3 | ## 1. Executive Summary | |
| 4 | 4 | ||
| 5 | - | Migrating existing projects away from GitHub Actions to a dedicated self-hosted CI system is difficult due to ecosystem inertia and configuration friction. | |
| 6 | - | ||
| 7 | - | This proposal outlines a strategy to **integrate `pici` with GitHub Actions as a drop-in execution engine**. Developers write standard, parallel bash workflows (`pico.sh`) using `zmx`, execute them identically on both local workstations and GitHub Actions runners, and gain **live, interactive terminal debugging on failure** powered by `pipe.pico.sh`. | |
| 8 | - | ||
| 9 | - | ```mermaid | |
| 10 | - | flowchart TD | |
| 11 | - | subgraph Local Environment | |
| 12 | - | Dev[Developer] -->|Runs locally| LocalScript["./pico.sh (or pici run)"] | |
| 13 | - | LocalScript -->|PTY Sessions| LocalZMX["zmx (Parallel Tasks)"] | |
| 14 | - | end | |
| 15 | - | ||
| 16 | - | subgraph GitHub Actions Runner | |
| 17 | - | GHA[GitHub Actions Workflow] --> InstallZMX["Install zmx"] | |
| 18 | - | InstallZMX --> RunScript["Run ./pici-debug.sh"] | |
| 19 | - | RunScript --> RunnerZMX["zmx sessions (Host & Docker)"] | |
| 20 | - | ||
| 21 | - | RunnerZMX -->|Success| GHASuccess["Finish Job (0)"] | |
| 22 | - | RunnerZMX -->|Failure| CatchError["Catch non-zero exit code"] | |
| 23 | - | ||
| 24 | - | CatchError --> StartBridge["Open bidirectional pipe to pipe.pico.sh"] | |
| 25 | - | StartBridge --> AllocPTY["Allocate interactive PTY (script / bash -i)"] | |
| 26 | - | end | |
| 27 | - | ||
| 28 | - | subgraph Pico Cloud Relay | |
| 29 | - | PipeRelay["ssh pipe.pico.sh pipe {topic}"] | |
| 30 | - | end | |
| 31 | - | ||
| 32 | - | StartBridge <-->|SSH stdin/stdout| PipeRelay | |
| 33 | - | Dev -.->|ssh -t pipe.pico.sh pipe {topic}| PipeRelay | |
| 34 | - | ``` | |
| 5 | + | Migrating existing projects away from GitHub Actions to a dedicated self-hosted CI system is difficult due to ecosystem inertia and configuration friction. | |
| 6 | + | ||
| 7 | + | This proposal outlines a strategy to provide an **official custom GitHub Action (`pico-sh/pici-action`)** powered by `pici run`. Developers write standard, parallel bash workflows (`pico.sh`) using `zmx`, execute them with 1:1 parity on both local workstations and GitHub Actions runners, and gain **live, interactive terminal debugging on failure** powered by native `pipe.pico.sh` bridging in Go. | |
| 35 | 8 | ||
| 36 | 9 | --- | |
| 37 | 10 | ||
| 38 | 11 | ## 2. Core Design Goals | |
| 39 | 12 | ||
| 40 | - | 1. **1:1 Local & Remote Parity**: The exact same `./pico.sh` runs locally and on CI without emulation layers or proprietary YAML DSLs. | |
| 41 | - | 2. **Native Docker Compatibility**: Seamless support for `docker run ...` steps in `pico.sh` within standard GitHub-hosted runners (`ubuntu-latest`). | |
| 42 | - | 3. **Interactive Debug on Failure ("Attach")**: When a task fails on GitHub Actions, the runner pauses and establishes an outbound reverse PTY bridge via `pipe.pico.sh`. | |
| 43 | - | 4. **Zero-Config Pico Authentication**: Key-based authentication directly through the developer's SSH private key—no usernames or external tunnel tokens required. | |
| 13 | + | 1. **1-Step GitHub Action Experience**: A custom composite action (`pico-sh/pici-action@v1`) that installs `pici` + `zmx`, configures SSH keys, executes the workflow in-place, and uploads artifacts with zero boilerplate. | |
| 14 | + | 2. **1:1 Local & Remote Parity**: `pici run` provides identical execution semantics across environments: metadata environment variable injection (`PICI_JOB`, `PICI_REPO`, `PICI_BRANCH`, `PICI_COMMIT`), `pico.sh` execution via `zmx`, live progress streaming, artifact rendering, and exit code propagation. | |
| 15 | + | 3. **Explicit Workspace Semantics**: By default, `pici run` rsyncs to an isolated `/tmp` workspace to keep local working trees clean. In CI environments where the runner is already an ephemeral container/VM, an explicit `--in-place` flag instructs `pici run` to execute directly without rsyncing. | |
| 16 | + | 4. **Native Docker Compatibility**: Mounts like `$(pwd):/app` work seamlessly in both local and CI environments. | |
| 17 | + | 5. **Native Go Failure Hook (`pici debug`)**: When a job fails, `pici` directly opens a secure, authenticated PTY bridge to `pipe.pico.sh` in Go, avoiding brittle bash FIFOs or background SSH wrappers. | |
| 18 | + | 6. **Security & Secret Scrubbing**: `pici` controls the debug shell environment and scrubs `GITHUB_TOKEN` and repository secrets before spawning interactive shells. | |
| 19 | + | ||
| 20 | + | --- | |
| 21 | + | ||
| 22 | + | ## 3. The Custom GitHub Action: `pico-sh/pici-action` | |
| 23 | + | ||
| 24 | + | We are creating and publishing an official custom action—`pico-sh/pici-action`—that serves as the turnkey entrypoint for GitHub Actions users. | |
| 25 | + | ||
| 26 | + | ### Standard Workflow (`.github/workflows/ci.yml`) | |
| 27 | + | ||
| 28 | + | With the custom action, adding Pici CI with remote debugging to any repo is a single step: | |
| 29 | + | ||
| 30 | + | ```yaml | |
| 31 | + | name: CI | |
| 32 | + | on: [push, pull_request] | |
| 33 | + | ||
| 34 | + | jobs: | |
| 35 | + | ci: | |
| 36 | + | runs-on: ubuntu-latest | |
| 37 | + | steps: | |
| 38 | + | - uses: actions/checkout@v4 | |
| 39 | + | ||
| 40 | + | - name: Run CI with Pici | |
| 41 | + | uses: picosh/pici-action@v1 | |
| 42 | + | with: | |
| 43 | + | pico_ssh_key: ${{ secrets.PICO_SSH_KEY }} | |
| 44 | + | ``` | |
| 45 | + | ||
| 46 | + | ### Action Configuration & Inputs | |
| 47 | + | ||
| 48 | + | | Input | Description | Default | | |
| 49 | + | | :--- | :--- | :--- | | |
| 50 | + | | `pico_ssh_key` | SSH private key registered with pico.sh for authenticated debug relay access. | `""` (optional, required for debug relay) | | |
| 51 | + | | `debug_on_fail` | Automatically open an interactive `pipe.pico.sh` debug bridge if a job fails. | `"false"` | | |
| 52 | + | ||
| 53 | + | ### What `pico-sh/pici-action` Does Under the Hood | |
| 54 | + | 1. **Tool Installation**: Installs the static `pici` and pinned `zmx` binaries (with SHA256 checksum verification) to `/usr/local/bin`. | |
| 55 | + | 2. **Credential Setup**: Writes `pico_ssh_key` to a secure temp keyfile or SSH agent if provided. | |
| 56 | + | 3. **Environment Propagation**: Passes GitHub Actions run metadata to `pici run`: | |
| 57 | + | - `PICI_JOB="${GITHUB_RUN_ID}"` | |
| 58 | + | - `PICI_EVENT="git.${GITHUB_EVENT_NAME}"` | |
| 59 | + | - `PICI_BRANCH="${GITHUB_REF_NAME}"` | |
| 60 | + | - `PICI_COMMIT="${GITHUB_SHA}"` | |
| 61 | + | 4. **Execution (`pici run`)**: Invokes `pici run --in-place` (with `--debug-on-fail` if enabled). | |
| 62 | + | 5. **Job Summaries & Artifact Upload**: | |
| 63 | + | - `pici` formats the run status table and failed logs into `$GITHUB_STEP_SUMMARY`. | |
| 64 | + | - If `upload_artifacts` is enabled, the action uploads `/tmp/pici-artifacts/<repo>/<jobid>/` as a workflow artifact. | |
| 65 | + | ||
| 66 | + | --- | |
| 67 | + | ||
| 68 | + | ## 4. Direct CLI Execution (Without the Action) | |
| 69 | + | ||
| 70 | + | For users who prefer manual step-by-step control or non-GitHub CI environments, `pici run` can be invoked directly: | |
| 71 | + | ||
| 72 | + | ```yaml | |
| 73 | + | - name: Run CI | |
| 74 | + | run: | | |
| 75 | + | pici run --in-place \ | |
| 76 | + | -e PICI_JOB="$GITHUB_RUN_ID" \ | |
| 77 | + | -e PICI_EVENT=git.push \ | |
| 78 | + | -e PICI_BRANCH="$GITHUB_REF_NAME" \ | |
| 79 | + | -e PICI_COMMIT="$GITHUB_SHA" | |
| 80 | + | ``` | |
| 44 | 81 | ||
| 45 | 82 | --- | |
| 46 | 83 | ||
| 47 | - | ## 3. Remote Debugging Architecture (`pipe.pico.sh`) | |
| 84 | + | ## 5. Native Go Debug Bridge (`pipe.pico.sh`) | |
| 48 | 85 | ||
| 49 | - | ### Why `pipe.pico.sh`? | |
| 50 | - | `pipe.pico.sh` provides authenticated, bidirectional byte streaming (`io.ReadWriter`) across topics over SSH. Because connections are outbound from the runner, no inbound ports or complex firewall configurations are necessary. | |
| 86 | + | Rather than relying on bash scripts with named FIFOs (`mkfifo`) and background `ssh -N` commands, the relay bridge is implemented directly in Go inside `pici`: | |
| 51 | 87 | ||
| 52 | - | 1. **Authentication & Topic Privacy**: | |
| 53 | - | - The GitHub Actions runner authenticates using the developer's `PICO_SSH_KEY` secret. | |
| 54 | - | - Topics created by an authenticated SSH key are automatically private and isolated to that account. | |
| 55 | - | 2. **PTY Bridging**: | |
| 56 | - | - Standard shell redirection (`A | B`) does not allocate a pseudo-terminal (PTY) and is unidirectional. | |
| 57 | - | - By creating named FIFOs (`mkfifo`) and wrapping an interactive shell with `script -q -f -c "bash -i"`, the bridge forwards full PTY escape sequences, window sizes, and raw terminal input. | |
| 88 | + | ### How it works | |
| 89 | + | 1. **Hook into Failure Path**: When `resolveJobExitCode()` detects a non-zero exit in `waitAndReport()` and debug mode is enabled (e.g. `--debug-on-fail` or `-e PICI_DEBUG=1`), `pici` initializes the debug session. | |
| 90 | + | 2. **Native SSH Dialing**: `pici` dials `pipe.pico.sh` using the existing `-pk` / `-ck` SSH key infrastructure. | |
| 91 | + | 3. **PTY Allocation (`creack/pty`)**: Spawns an interactive shell (`bash -i` or `$SHELL`) connected to the bidirectional SSH stream. | |
| 92 | + | 4. **Attach Instructions & Notifications**: Prints the `ssh -t pipe.pico.sh pipe <topic>` command to stdout and appends it to `$GITHUB_STEP_SUMMARY`. | |
| 93 | + | 5. **Idle Timeout & Clean Exit**: Waits for a connection with an idle timeout (e.g., 15 minutes). Once closed or timed out, `pici` terminates and exits with the original non-zero job exit code. | |
| 94 | + | 6. **Standalone `pici debug`**: `pici debug <repo>/<jobid>` attaches to running or stalled jobs by targeting the deterministic session prefix (`ci.<repo>.<jobid>.`). | |
| 95 | + | ||
| 96 | + | ### Security Advantages | |
| 97 | + | - **Environment Scrubbing**: Sensitive CI environment variables (`GITHUB_TOKEN`, `ACTIONS_RUNTIME_TOKEN`, injected secrets) are stripped from the debug shell process environment before launching. | |
| 98 | + | - **Host Key Pinning**: The `pipe.pico.sh` host key is verified natively in Go without passing `-o StrictHostKeyChecking=no`. | |
| 58 | 99 | ||
| 59 | 100 | --- | |
| 60 | 101 | ||
| 61 | - | ## 4. Initial Implementation: `pici-debug.sh` | |
| 102 | + | ## 6. Implementation & Verification Plan (Test Repository) | |
| 103 | + | ||
| 104 | + | To validate the complete flow end-to-end before public release, we will use a dedicated test repository (e.g., `picosh/pici-test`) to confirm local parity, GitHub Actions execution, and interactive failure recovery. | |
| 105 | + | ||
| 106 | + | ### Step 1: Create the Test Repository Structure | |
| 107 | + | ||
| 108 | + | ``` | |
| 109 | + | pici-test/ | |
| 110 | + | ├── .github/ | |
| 111 | + | │ └── workflows/ | |
| 112 | + | │ ├── ci-action.yml # Tests the custom picosh/pici-action | |
| 113 | + | │ └── ci-direct.yml # Tests direct pici binary execution | |
| 114 | + | ├── src/ | |
| 115 | + | │ └── main_test.go | |
| 116 | + | ├── pico.sh # Core CI script with configurable test modes | |
| 117 | + | └── README.md | |
| 118 | + | ``` | |
| 62 | 119 | ||
| 63 | - | A minimal, pure-bash wrapper that can be placed in any repository to execute CI and open the debug bridge on failure. | |
| 120 | + | ### Step 2: Implement the Configurable `pico.sh` | |
| 121 | + | ||
| 122 | + | The test script supports environment variables to deterministically simulate success, failure, Docker steps, and hangs: | |
| 64 | 123 | ||
| 65 | 124 | ```bash | |
| 66 | 125 | #!/usr/bin/env bash | |
| 67 | - | set -e | |
| 68 | - | ||
| 69 | - | TOPIC="pici-${GITHUB_RUN_ID:-$RANDOM}" | |
| 70 | - | FIFO_IN=$(mktemp -u /tmp/pici-in.XXXXXX) | |
| 71 | - | FIFO_OUT=$(mktemp -u /tmp/pici-out.XXXXXX) | |
| 72 | - | ||
| 73 | - | cleanup() { | |
| 74 | - | kill "$SSH_PID" 2>/dev/null || true | |
| 75 | - | rm -f "$FIFO_IN" "$FIFO_OUT" | |
| 76 | - | } | |
| 77 | - | trap cleanup EXIT | |
| 78 | - | ||
| 79 | - | # 1. Run the CI script | |
| 80 | - | echo "==> Running pico.sh..." | |
| 81 | - | if ./pico.sh; then | |
| 82 | - | echo "==> CI succeeded!" | |
| 83 | - | exit 0 | |
| 84 | - | fi | |
| 126 | + | set -euo pipefail | |
| 85 | 127 | ||
| 86 | - | CI_EXIT_CODE=$? | |
| 128 | + | JOB_ID="${PICI_JOB:-local}" | |
| 129 | + | REPO="${PICI_REPO:-pici-test}" | |
| 130 | + | EVENT="${PICI_EVENT:-local}" | |
| 131 | + | ZMX_SESSION_PREFIX="${ZMX_SESSION_PREFIX:-local.}" | |
| 87 | 132 | ||
| 88 | - | # 2. Check if debug mode is enabled | |
| 89 | - | if [ "${DEBUG_ON_FAIL:-1}" != "1" ]; then | |
| 90 | - | exit "$CI_EXIT_CODE" | |
| 133 | + | echo "==> Running CI for $REPO (job: $JOB_ID, event: $EVENT)" | |
| 134 | + | ||
| 135 | + | # 1. Parallel test steps | |
| 136 | + | zmx run unit-tests -d bash -c 'echo "Running unit tests..."; sleep 2; echo "Tests passed!"' | |
| 137 | + | zmx run lint -d bash -c 'echo "Running linter..."; sleep 1; echo "Lint clean!"' | |
| 138 | + | ||
| 139 | + | # 2. Simulated failure scenario (when TEST_FAILURE=1) | |
| 140 | + | if [ "${TEST_FAILURE:-0}" = "1" ]; then | |
| 141 | + | zmx run failing-step -d bash -c 'echo "Simulating fatal compilation error..."; sleep 2; exit 42' | |
| 91 | 142 | fi | |
| 92 | 143 | ||
| 93 | - | echo "================================================================" | |
| 94 | - | echo "🔴 CI FAILED! Debug bridge active on pipe.pico.sh" | |
| 95 | - | echo "Connect from your terminal using:" | |
| 96 | - | echo " ssh -t pipe.pico.sh pipe $TOPIC" | |
| 97 | - | echo "================================================================" | |
| 98 | - | ||
| 99 | - | # 3. Create FIFOs for bidirectional PTY stream | |
| 100 | - | mkfifo "$FIFO_IN" "$FIFO_OUT" | |
| 101 | - | ||
| 102 | - | # 4. Connect background bridge to pipe.pico.sh using SSH key | |
| 103 | - | ssh -N -o StrictHostKeyChecking=no \ | |
| 104 | - | -o ServerAliveInterval=15 \ | |
| 105 | - | pipe.pico.sh "pipe $TOPIC" < "$FIFO_OUT" > "$FIFO_IN" & | |
| 106 | - | SSH_PID=$! | |
| 107 | - | ||
| 108 | - | # 5. Spawn interactive PTY bash shell connected to the pipe | |
| 109 | - | script -q -f -c " | |
| 110 | - | echo '=== Connected to GitHub Actions Debug Shell ===' | |
| 111 | - | echo '' | |
| 112 | - | echo 'Current zmx sessions:' | |
| 113 | - | zmx list || true | |
| 114 | - | echo '' | |
| 115 | - | echo 'Type exit to finish CI run.' | |
| 116 | - | bash -i | |
| 117 | - | " /dev/null < "$FIFO_IN" > "$FIFO_OUT" | |
| 118 | - | ||
| 119 | - | exit "$CI_EXIT_CODE" | |
| 120 | - | ``` | |
| 144 | + | # 3. Simulated hang scenario (when TEST_HANG=1) | |
| 145 | + | if [ "${TEST_HANG:-0}" = "1" ]; then | |
| 146 | + | zmx run hung-test -d bash -c 'echo "Simulating hung test process..."; sleep 3600' | |
| 147 | + | fi | |
| 121 | 148 | ||
| 122 | - | --- | |
| 149 | + | # 4. Docker container step | |
| 150 | + | zmx run docker-step -d docker run --rm alpine:latest sh -c "echo 'Docker step executed inside alpine container'" | |
| 151 | + | ||
| 152 | + | # 5. Wait for all steps | |
| 153 | + | zmx wait "*" | |
| 154 | + | ||
| 155 | + | printf "\x1b[32mAll test steps succeeded!\x1b[0m\n" | |
| 156 | + | ``` | |
| 123 | 157 | ||
| 124 | - | ## 5. GitHub Actions Workflow Integration | |
| 158 | + | ### Step 3: Configure GitHub Actions Workflows | |
| 125 | 159 | ||
| 126 | - | ### Workflow Example (`.github/workflows/ci.yml`) | |
| 160 | + | #### Test Workflow 1: Using `picosh/pici-action` (`.github/workflows/ci-action.yml`) | |
| 127 | 161 | ||
| 128 | 162 | ```yaml | |
| 129 | - | name: CI | |
| 130 | - | on: [push, pull_request] | |
| 163 | + | name: Pici Action Validation | |
| 164 | + | on: | |
| 165 | + | push: | |
| 166 | + | workflow_dispatch: | |
| 167 | + | inputs: | |
| 168 | + | test_failure: | |
| 169 | + | description: "Simulate step failure (exit 42)" | |
| 170 | + | type: boolean | |
| 171 | + | default: false | |
| 131 | 172 | ||
| 132 | 173 | jobs: | |
| 133 | - | pici: | |
| 174 | + | test-ci: | |
| 134 | 175 | runs-on: ubuntu-latest | |
| 135 | 176 | steps: | |
| 136 | 177 | - uses: actions/checkout@v4 | |
| 137 | 178 | ||
| 138 | - | - name: Install zmx | |
| 139 | - | run: | | |
| 140 | - | # Fetch and install zmx binary | |
| 141 | - | sudo curl -fsSL https://github.com/picosh/zmx/releases/latest/download/zmx_linux_amd64 -o /usr/local/bin/zmx | |
| 142 | - | sudo chmod +x /usr/local/bin/zmx | |
| 143 | - | ||
| 144 | - | - name: Setup Pico SSH Key | |
| 145 | - | if: always() | |
| 146 | - | env: | |
| 147 | - | PICO_SSH_KEY: ${{ secrets.PICO_SSH_KEY }} | |
| 148 | - | run: | | |
| 149 | - | mkdir -p ~/.ssh | |
| 150 | - | echo "$PICO_SSH_KEY" > ~/.ssh/id_ed25519 | |
| 151 | - | chmod 600 ~/.ssh/id_ed25519 | |
| 152 | - | ||
| 153 | - | - name: Run CI with Pici Debug Bridge | |
| 179 | + | - name: Run Pici CI | |
| 180 | + | uses: picosh/pici-action@main | |
| 181 | + | with: | |
| 182 | + | pico_ssh_key: ${{ secrets.PICO_SSH_KEY }} | |
| 183 | + | debug_on_fail: "true" | |
| 154 | 184 | env: | |
| 155 | - | DEBUG_ON_FAIL: "1" | |
| 156 | - | run: ./pici-debug.sh | |
| 185 | + | TEST_FAILURE: ${{ github.event.inputs.test_failure && '1' || '0' }} | |
| 157 | 186 | ``` | |
| 158 | 187 | ||
| 159 | - | --- | |
| 160 | - | ||
| 161 | - | ## 6. Docker Considerations | |
| 188 | + | ### Step 4: Verification Matrix & Acceptance Criteria | |
| 162 | 189 | ||
| 163 | - | - **Out of the box**: GitHub Actions `ubuntu-latest` runners come with Docker pre-installed and running. `docker run` commands inside `pico.sh` execute directly against the host Docker daemon. | |
| 164 | - | - **Debugging Docker Containers**: When attached via the debug shell: | |
| 165 | - | - Users can inspect running/stopped containers via `docker ps -a`. | |
| 166 | - | - Users can enter failed containers using `docker exec -it <container_id> /bin/bash`. | |
| 167 | - | - For steps using `--rm`, users can rerun the command without `--rm` inside the debug shell to inspect intermediate layers. | |
| 168 | - | ||
| 169 | - | --- | |
| 170 | - | ||
| 171 | - | ## 7. Adoption & Evolution Roadmap | |
| 172 | - | ||
| 173 | - | | Phase | Deliverable | Description | | |
| 174 | - | | :--- | :--- | :--- | | |
| 175 | - | | **Phase 1 (Immediate)** | `pici-debug.sh` Bash Script | Pure script wrapper requiring zero changes to `pici` or Go codebases. | | |
| 176 | - | | **Phase 2 (Packaging)** | `pico-sh/pici-action` | Dedicated GitHub Action packaging `zmx` installation, key setup, and step summary outputs. | | |
| 177 | - | | **Phase 3 (Native Go Bridge)** | `pici debug` Subcommand | Built-in `pici` CLI command implementing native SSH dialing to `pipe.pico.sh` with `creack/pty`. | | |
| 178 | - | | **Phase 4 (Reporting)** | Rich Step Summaries | Exporting `zmx` session history (`zmx history --html`) directly into `$GITHUB_STEP_SUMMARY`. | | |
| 190 | + | | Test Scenario | Trigger | Expected GHA Behavior | Acceptance Criteria | | |
| 191 | + | | :--- | :--- | :--- | :--- | | |
| 192 | + | | **1. Happy Path** | Push commit with `TEST_FAILURE=0` | All `zmx` sessions pass. | • Workflow exits with `0` (green).<br>• GHA Step Summary displays full table with durations.<br>• HTML report uploaded as workflow artifact. | | |
| 193 | + | | **2. Failure & Remote Attach** | Dispatch workflow with `test_failure=true` | `failing-step` exits `42`. | • `waitAndReport` catches exit `42`.<br>• Relay bridge opens on `pipe.pico.sh`.<br>• `ssh -t pipe.pico.sh pipe <topic>` printed to logs and step summary.<br>• Connecting via terminal drops into interactive debug shell with active `zmx` sessions.<br>• Exiting debug shell ends GHA job with code `42` (red). | | |
| 194 | + | | **3. Secret Scrubbing Audit** | During interactive debug session | Inspect environment variables inside attached shell. | • `GITHUB_TOKEN`, `ACTIONS_RUNTIME_TOKEN`, and `PICO_SSH_KEY` are absent from `env`. | | |
| 195 | + | | **4. Docker Step Execution** | Standard run | Alpine container runs via `docker run --rm`. | • Docker mounts `$(pwd)` correctly in-place.<br>• Container logs captured in `zmx history docker-step`. | | |
| 196 | + | | **5. Idle Timeout Cleanup** | Failure triggered without developer attaching | Wait for configured idle timeout (e.g. 15m). | • Bridge cleanly closes upon timeout.<br>• GHA runner exits with the original step failure code (does not hang indefinitely). | |