Skip to content

Probe the Codex sandbox before coding and declare environment skips (#43, slice 3) #403

Closed

Description

@DanielNetzer

Third slice of #43: a sandbox check before each Codex coding run, with the result recorded and passed to the harness.

Problem

Forgecell finds out what a Codex coding sandbox denies only after the model has spent a run hitting it. codex sandbox (Codex CLI ≥ 0.167) runs a command inside the same seatbelt or landlock sandbox with no model call. A manual probe on macOS showed that a workspace-write sandbox denies writing ~/Library/Caches/go-build and denies ps, while writing the workspace works.

Decisions

  1. Codex only, coding runs only. Before the approved-plan coding stage starts a native Codex coding run, the Lab runs a fixed probe through the same provider executable: codex sandbox -c sandbox_mode="workspace-write", the same --add-dir / writable_roots and shell_environment_policy.set overrides the coding run will get, and -C <workspace>. The probe is a fixed POSIX sh -c script, not repository code. Other harnesses get no probe.

  2. What it checks. Each check is one line of name=ok or name=denied:

    • workspace-write: create and remove a temporary file in the workspace;
    • harness-cache-write: create and remove one in the harness GOCACHE;
    • spawn: run a child process;
    • ps: run ps -p $$;
    • localhost-bind: open a listening TCP socket on 127.0.0.1, only if python3 or nc is on PATH inside the sandbox, otherwise unknown.

    A probe that can't run at all (an old Codex without sandbox, or a timeout of 20 s) is recorded as unavailable with the reason. Coding still starts, as today.

  3. Recorded, then passed on. The result goes on the coding attempt (sandboxProbe: the Codex version, each check and its state). The coding instruction gets one short sentence naming the denied capabilities, for example "The sandbox denies: ps, localhost-bind. Don't run checks that need them; the Lab runs the approved checks outside the sandbox." If nothing is denied, the instruction is unchanged.

  4. Fail closed on our own cache. If harness-cache-write is denied, coding doesn't start: the Molecule is blocked with "Harness Go cache is not writable inside the Codex sandbox", because the run would hit it anyway.

  5. Declared environment skips, for this repository's tests. The coding run's environment gets FORGECELL_SANDBOX_DENIES set to the denied capabilities, comma-separated (unset when none are denied). Add a small test helper to the Go module, for example testenv.SkipIfSandboxDenies(t, "ps"), which skips with an explicit reason when that capability is listed. Use it in the tests that need ps (TestIssueQueueCancellationStopsPipeHoldingDescendant) or a local socket (the installer HTTPS lifecycle tests). CI and the Lab's checks never set the variable, so those tests still run there.

  6. Protocol bump if the probe changes the adapter contract: one past whatever main has when this is planned.

Acceptance

  • Unit tests with a fake codex that echoes chosen probe lines: the probe result is parsed, recorded on the attempt, and turned into the instruction sentence and FORGECELL_SANDBOX_DENIES.
  • A fake probe that reports harness-cache-write=denied blocks the stage before any coding process starts.
  • A missing codex sandbox subcommand or a timeout records unavailable and coding proceeds.
  • Claude Code and Cursor runs, and Codex analysis and review runs, get no probe and no new variable.
  • With FORGECELL_SANDBOX_DENIES=ps, the ps-dependent test skips with a reason that names it; without it, the test runs as before.
  • No real Codex, model or network in tests.

Answers from the first intake

  1. Where the coding stage lives: resumeApprovedIssue in lab/internal/molecule/readiness_run.go. That is where Give Codex coding runs a writable Lab-owned Go build cache (#43, slice 1) #396 attaches the harness cache (resources.HarnessGoCache, harness.WithHarnessCache), creates the HarnessAttempt and blocks the stage with finishFailed. The probe, the recorded sandboxProbe, the instruction sentence, FORGECELL_SANDBOX_DENIES and the Decision 4 block all go there, next to the cache code.
  2. Local-socket test skips are out of this slice. The installer HTTPS lifecycle check is the Python script scripts/test-native-installer.py, which a Go helper can't skip. The Go tests that open local sockets are spread through lab/internal/ui (httptest). The probe still checks localhost-bind, and the coding instruction still names it when it's denied, so the harness knows not to run those tests inside the sandbox. Decision 5 now covers only ps: add SkipIfSandboxDenies and use it in TestIssueQueueCancellationStopsPipeHoldingDescendant (lab/internal/intake/queue_test.go). Other ps-dependent tests the planner finds may use it too.

Activity

  1. added
    enhancementNew feature or request
    Next improvementCapabilities, composition, evidence and usability after the reviewed-PR MVP.
    on Nov 7, 2026
  2. DanielNetzer commented on Nov 8, 2026

    @DanielNetzer
    OwnerAuthor

    Fixed in #407.

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

    Next improvementCapabilities, composition, evidence and usability after the reviewed-PR MVP.enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions