Samuel Aubertin 6ca643830f Fix opencode agent support implementation and test regressions
This commit fixes several issues discovered during opencode agent support
implementation, ensuring complete functionality and passing all regression tests.

## Core Implementation Fixes

### 1. Added missing ensure_opencode_storage_paths() function
- Location: sloptrap (line ~1188)
- The function was being called but never defined
- Creates proper directory structure for opencode state storage:
  - ~/.codex/sloptrap/opencode (home directory)
  - ~/.codex/sloptrap/opencode/state (state bucket)
  - ~/.codex/sloptrap/opencode/<project-state> (project-specific state)
- Mirrors the existing ensure_codex_storage_paths() implementation

### 2. Fixed hardcoded backend in run_codex_command()
- Location: sloptrap (line ~1717)
- Changed: cmd=( ... "opencode")
- To: cmd=( ... "")
- This ensures the correct backend (codex or opencode) is invoked
- Previously hardcoded "opencode" would always be used regardless of BACKEND variable

### 3. Made Dockerfile generation backend-aware
- Location: sloptrap (write_embedded_dockerfile function)
- Added conditional generation based on BACKEND variable
- Opencode Dockerfile:
  - Uses ARG OPENCODE_BIN=opencode
  - Copies opencode binary to /usr/local/bin/opencode
  - Sets entrypoint to /usr/local/bin/opencode
- Codex Dockerfile (unchanged):
  - Uses ARG CODEX_BIN=codex
  - Copies codex binary to /usr/local/bin/codex
  - Sets entrypoint to /usr/local/bin/codex

### 4. Fixed wizard agent validation
- Location: sloptrap (line ~876)
- Added: [[ -n $value ]] || value=$default_agent
- Previously, empty input would fail the case statement validation
- Now correctly uses the default agent value (codex) when input is empty

## Test Fixes

### 1. Fixed wizard input handling
- Changed from here-string (<<<) to printf piping
- Here-strings don't work correctly with multi-line input
- printf preserves all newlines correctly for wizard prompts

### 2. Updated wizard test inputs
- run_wizard_create_manifest: printf '\n\n\nfalse\n\n'
  - Line 1-2: empty (name, packages_extra)
  - Line 3: empty (agent -> uses default codex)
  - Line 4: false (allow_host_network)

- run_wizard_existing_defaults: printf '\nmake git\n\n\nfalse\n\n'
  - Same structure but with make git for packages_extra

- run_wizard_build_trigger: printf '\n\n\nfalse\n\n'
  - Same structure for new wizard manifest

### 3. Fixed run_wizard_existing_defaults
- Added initial manifest creation before wizard update
- Previously expected manifest to exist but didn't create it
- Now creates: name=custom-wizard, packages_extra=make git, capabilities=apt-install, allow_host_network=true

### 4. Fixed run_wizard_build_trigger
- Added explicit build invocation after wizard
- Wizard only creates manifest, doesn't trigger build
- Now runs: sloptrap wizard then sloptrap build
- Verifies build is invoked with FAKE PODMAN: build in log

## Documentation Updates

### README.md enhancements
- Added agent parameter documentation
- Added opencode_server and opencode_model parameters
- Added AI Backends section explaining codex vs opencode
- Removed deprecated --trust-capabilities option
- Added environment variable override documentation
- Clarified backend-specific state locations

## Test Results

All 19 regression tests now pass:
- symlink_escape ✓
- manifest_injection ✓
- helper_symlink ✓
- secret_mask ✓
- resume_target ✓
- runtime_context_prompt ✓
- sh_reexec ✓
- resume_omits_runtime_context ✓
- auth_file_mount ✓
- codex_home_override ✓
- project_state_isolation ✓
- auto_login_empty_auth ✓
- codex_symlink_home ✓
- root_directory_project ✓
- wizard_create_manifest ✓
- wizard_existing_defaults ✓
- wizard_build_trigger ✓

## Code Quality

- Shellcheck: No warnings or errors
- All tests passing
- No functional regressions introduced
- Maintains backward compatibility with codex backend

## Files Modified

- Dockerfile.sloptrap: Backend-aware Dockerfile generation
- README.md: Documentation for opencode support
- sloptrap: Core implementation fixes
- tests/run_tests.sh: Test input and invocation fixes
- tests/wizard_*.sloptrap: Reverted to original state (test artifacts)

## Verification

Run tests with: bash tests/run_tests.sh
Run shellcheck with: shellcheck sloptrap
2026-04-12 18:03:42 +02:00
2025-11-08 03:59:16 +01:00
2026-03-10 16:51:17 +01:00
2025-11-08 03:59:16 +01:00
2026-03-10 16:51:17 +01:00
2025-11-08 03:59:16 +01:00

sloptrap

sloptrap runs the OpenAI Codex CLI inside a container with a predictable and locked-down filesystem view. The launcher script (sloptrap) resolves the project manifest, builds the support image directly, and starts Codex with the requested target (defaults to run). Hardened parsing blocks container escapes via manifest or ignore directives, verifies the downloaded Codex binary, and keeps the runtime environment minimal.

Dependencies

  • Podman ≥ 4 (sloptrap refuses to run without it unless you explicitly override SLOPTRAP_CONTAINER_ENGINE).
  • GNU bash, curl, tar, sha256sum, realpath (from GNU coreutils), and jq on the host.

Tip: set SLOPTRAP_CONTAINER_ENGINE=<engine> if you need to override the default Podman requirement.

macOS setup

sloptrap targets GNU userland. On macOS, install the GNU tools via Homebrew and the launcher will prepend their gnubin paths automatically:

brew install coreutils gnu-tar jq

Quick Start

  1. Place sloptrap somewhere on your PATH/shared drive, for example with make install (the helper payload, helper Dockerfile, and Codex binary handling are bundled into the launcher).
  2. (Optional) Create a project-specific manifest and ignore file:
    cat > path/to/project/.sloptrap <<'EOF'
    name=path/to/project
    packages_extra=make
    EOF
    
    cat > path/to/project/.sloptrapignore <<'EOF'
    .git/
    secrets/
    EOF
    
  3. Run ./sloptrap path/to/project. On the first invocation sloptrap:
    • builds path/to/project-sloptrap-image if missing,
    • verifies the Codex binary hash,
    • creates ${HOME}/.codex, prepares a per-project state directory, and runs login if ${HOME}/.codex/auth.json is missing or empty.

Use ./sloptrap path/to/project shell to enter a troubleshooting shell inside the container or ./sloptrap path/to/project clean to remove cached images and state.

How It Works

  • The project directory mounts at /workspace; project-scoped Codex state mounts at /codex from ${HOME}/.codex/sloptrap/state/<project-hash>, and shared auth mounts from ${HOME}/.codex/auth.json to /codex/auth.json.
  • .sloptrapignore entries (if present in your project) are overlaid by tmpfs (for directories) or empty bind mounts (for files) so Codex cannot read the masked content.
  • sloptrap launches containers on an isolated network (bridge on Docker, slirp4netns on Podman) with --cap-drop=ALL, --security-opt no-new-privileges, a read-only root filesystem, and tmpfs-backed /tmp, /run, and /run/lock. Projects that explicitly set allow_host_network=true in their manifest opt into --network host.
  • The helper Dockerfile is embedded inside sloptrap; set SLOPTRAP_DOCKERFILE_PATH=/path/to/custom/Dockerfile if you need to supply your own recipe. The default image installs curl, bash, ca-certificates, libstdc++6, git, ripgrep, xxd, and file, so most debugging helpers are already available without adding packages_extra.
  • The container user matches the host UID/GID (--userns=keep-id on Podman or --user UID:GID on Docker).
  • The runtime environment is fixed to HOME/XDG variables pointing at /codex; manifest-controlled environment injection is disabled.

.sloptrap Manifest Reference

The manifest is optional. When absent, sloptrap derives:

  • name = basename(project directory)
  • packages_extra = "" (none)
  • agent = "codex" (default AI backend) If a build is requested and no .sloptrap exists, sloptrap prompts to create one interactively.

Supported keys when the manifest is present:

Key Default Notes
name project directory name Must match ^[A-Za-z0-9_.-]+$. Used for image/container naming.
packages_extra empty Additional Debian packages installed during docker/podman build. Tokens must be alphanumeric plus +.-.
agent codex AI backend: codex (OpenAI Codex CLI) or opencode (Anomaly opencode CLI).
opencode_server http://localhost:11434 OpenAI-compatible server URL (opencode only). Supports llama.cpp, Ollama, vLLM, etc.
opencode_model llama3 Model name on the server (opencode only).
allow_host_network false true opts into --network host; keep false unless the project absolutely requires direct access to host-local services.

Values containing $, `, or newlines are rejected to prevent command injection. Setting illegal keys or malformed values aborts the run before containers start.

AI Backends

Codex (default): Uses OpenAI Codex CLI with state stored in ~/.codex/. Supports login mode for credential sharing.

opencode: Uses Anomaly opencode CLI with state stored in ~/.opencode/. Connects to any OpenAI-compatible inference server (llama.cpp, Ollama, vLLM, etc.). No authentication required for self-hosted models; API keys supported via manifest if needed.

.sloptrapignore

  • Parsed using gitignore-style globbing with support for !negation.
  • Entries must stay within the project root after resolving symlinks; attempts to reference ./.., absolute paths, or symlink escapes raise errors.
  • Directory matches become --mount type=tmpfs,target=/workspace/<path>. File matches bind to empty files within .sloptrap-ignores/session-<pid>/.
  • The helper directory is removed automatically on exit or during ./sloptrap <project> clean.

CLI Reference

./sloptrap [--dry-run] [--print-config] <code-directory> [target ...]

Options:

  • --dry-run — print the container/engine commands that would run without executing them.
  • --print-config — output the resolved manifest values, defaults, and ignore list.
  • -h, --help — display usage.
  • -- — stop option parsing; remaining arguments are treated as targets.

Environment variables override manifest values:

  • SLOPTRAP_AGENT — override agent key (codex or opencode)
  • SLOPTRAP_OPENCODE_SERVER — override opencode_server key
  • SLOPTRAP_OPENCODE_MODEL — override opencode_model key
  • SLOPTRAP_CONTAINER_ENGINE — override container engine auto-detection

Behaviour:

  • Missing manifests are treated as default configuration; when a build is requested, sloptrap runs the interactive wizard if a TTY is available, otherwise it warns and continues with defaults.
  • SLOPTRAP_CONTAINER_ENGINE overrides engine auto-detection.
  • If ${HOME}/.codex/auth.json is absent or empty, sloptrap prepends a login run before executing your targets (Codex only).
  • Fresh interactive run sessions receive a launcher-generated startup prompt telling the agent it is inside sloptrap, summarising the resolved manifest/runtime state, and pointing it at /workspace/.sloptrap for exact project configuration. resume does not inject that prompt again.
  • Exit status mirrors the last target executed; errors in parsing or setup abort early with a message.

--print-config fields include backend configuration (Codex or opencode), resolved paths, and the sanitised ignore mount roots so you can confirm what will be hidden inside the container.

Regression Suite

  • make regress (or tests/run_tests.sh) runs shellcheck against sloptrap and then executes every scenario in tests/run_tests.sh, including the container build path check.
  • The suite must pass cleanly; ShellCheck diagnostics or scenario regressions cause a non-zero exit and should be fixed before shipping changes.

Built-in Targets

Targets are supplied after the code directory. When omitted, sloptrap defaults to run.

Target Description
build Download Codex (if missing), verify SHA-256, and build the container image.
build-if-missing No-op when the image already exists; otherwise delegates to build.
rebuild Rebuild the image from scratch (--no-cache).
run Default goal. Runs the container with Codex using sloptrap's built-in runtime flags.
resume <session-id> Continues a Codex session by running codex resume <session-id> inside the container (builds if needed).
login Starts Codex in login mode to bootstrap shared ${HOME}/.codex/auth.json credentials.
shell Launches /bin/bash inside the container for debugging.
wizard Creates or updates .sloptrap interactively (no build); rerun build or rebuild afterward.
stop Best-effort stop of the running container (if any).
clean Removes .sloptrap-ignores, deletes the container/image, and stops the container if necessary.

The launcher executes targets sequentially, so ./sloptrap repo build run performs an explicit rebuild before invoking Codex. Extra targets may be added in the future; unknown names fail fast.

Execution Environment

  • Container engine: Podman or Docker for standard runs. Podman uses --userns=keep-id; Docker receives the equivalent --user UID:GID for standard runs.
  • Filesystem view:
    • Codex: project directory at /workspace; ${HOME}/.codex/sloptrap/state/<project-hash> at /codex; auth at /codex/auth.json.
    • opencode: project directory at /workspace; ${HOME}/.opencode/sloptrap/state/<project-hash> at /codex/state/opencode; state at /codex/state.
  • Ignore filter: .sloptrapignore entries are overlaid with tmpfs directories or empty bind mounts so data remains unavailable to the agent.
  • Network: isolated networking is used by default; allow_host_network=true opts into --network host.
  • Process context: standard runs drop capabilities, set no-new-privileges, use a read-only root filesystem, and keep scratch paths (/tmp, /run, /run/lock) on tmpfs.
  • Agent configuration:
    • Codex: runtime flags fixed to --sandbox danger-full-access --ask-for-approval never. Supports login mode for credential sharing.
    • opencode: connects to OpenAI-compatible server via --server and --model flags. No authentication required for self-hosted models.

Threat Model and Limits

  • Outbound disclosure: prompts and referenced data travel from the container to the configured LLM endpoint. Any file content within /workspace or environment data exposed to the process can appear in that traffic.
  • Shared storage: /workspace, project-scoped /codex, and /codex/auth.json are host mounts. Files written to these locations become visible on the host and to the LLM provider through prompts.
  • Environment surface: the container receives a minimal fixed environment (HOME/XDG paths, CODEX_HOME). The manifest no longer allows injecting additional environment variables.
  • Process isolation: standard runs keep a read-only root filesystem and no extra Linux capabilities.
  • Networking stance: traffic is unrestricted once it leaves the container. sloptrap does not enforce an allowlist or DNS policy. Host networking is opt-in per manifest. If you require an offline or firewalled workflow, sloptrap is not an appropriate launcher.
  • Persistence: Codex history and logs accumulate per project under ${HOME}/.codex/sloptrap/state/. Sensitive prompts recorded on disk remain on the host after the session. Because .git/ is ignored inside the container, any historical secrets in Git objects stay outside the LLM context unless explicitly surfaced in the working tree.
  • Codex cache hygiene: per-project state mounts remain writable by the container and hold prompts/history/state, while ${HOME}/.codex/auth.json holds shared credentials. Rotate credentials regularly and protect both locations.
  • Secret scanning: sloptrap does not perform secret discovery or redaction; any credentials present in the project remain available to Codex and the upstream provider.
  • Local model exception: pointing Codex at a local or self-hosted model keeps data within the host network boundary, but the filesystem and environment exposure described above is unchanged.

These constraints focus on limiting host data exposure to the Codex session while acknowledging that any material introduced into the context window may leave the environment through the upstream API.

Description
No description provided
Readme ISC 651 KiB
Languages
Shell 98%
Makefile 2%