Skip to content

feat(call-agent): optional cwd and permission-mode for sandboxed runs - #5

Merged
joepetjr merged 3 commits into
mainfrom
feat/call-agent-cwd-and-permission-mode-rebased
Aug 3, 2026
Merged

joepetjr merged 3 commits into
mainfrom
feat/call-agent-cwd-and-permission-mode-rebased

Conversation

@joepetjr

@joepetjr joepetjr commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Two optional keyword-only arguments on call_agent, threaded through fan_out and deliver.run:

  • cwd — runs the claude subprocess in that directory, validated to exist first so an
    unusable path raises AgentError before any process starts. Inside fan_out that surfaces as
    the unit's _error row instead of taking the batch down.
  • permission_mode — passed to claude --permission-mode. The accepted modes stay the CLI's
    vocabulary, not the runner's, so a mode the CLI adds later needs no release here. Flag name was
    read from real claude --help output, not assumed.

A campaign confines its worker, advisor and grader alike, so an escalation cannot step outside
the sandbox its worker ran in.

Why

A consumer's eval runner replays recorded prompts, some of them action-shaped ("ship it"). It must
run them confined to a throwaway fixture directory under a restrictive permission mode. It could
not, so it refuses to run replay mode at all and nightly evals are blocked on this package. Verified
against this branch — the consumer's gate flips on its own with no change on its side:

replay_isolation_status() -> (True, 'pinnable via cwd + permission_mode')

(It returns (False, 'driver exposes no workdir or permission-mode parameter…') against main.)

Backward compatibility

Both default to None; omitting them changes nothing. No --permission-mode in the argv, and no
cwd= keyword on the subprocess.run call at all
— not even cwd=None. That is enforced by
construction: the pre-existing test stub is def fake_run(cmd, capture_output, text, timeout) and
would TypeError on an unconditional cwd=. Injected call= functions keep their four-positional
signature.

Supersedes #3

#3 was refused by the merge wrapper on a genuine stale base — main moved ahead with the installer
PR, which also edits CHANGELOG.md and README.md. Rebasing in place needs a force-push, which is
blocked in this environment, so the commit is re-pushed on a fresh branch.

Two changes made while resolving, both toward this repo's stated convention:

  • The changelog entry now sits under ## Unreleased alongside the installer entry, per
    CONTRIBUTING.md ("Add your entry under an ## Unreleased heading"). feat(call-agent): optional cwd and permission-mode for sandboxed runs #3 had created a ## 0.2.0
    heading.
  • The version bump is reverted — pyproject.toml and __version__ stay at 0.1.0. The
    installer PR did not bump either, so this repo bumps at release time, not per PR. Consumers can
    pin to the merge commit until a release is cut.

Test plan

  • python3 -m pytest -q → 73 passed
  • Consumer gate probe run against this branch (output above)
  • No 0.2.0 string remains in pyproject.toml or the package
  • Cherry-picked onto current main (6002f84); CHANGELOG conflict resolved by keeping both entries

main cut the 0.1.1 release while this branch was open. The automatic
merge folded this branch's changelog entry into the 0.1.1 section, which
would have claimed the feature shipped in a release it is not in; moved
back under a fresh Unreleased heading above 0.1.1, with the installer
entry left in 0.1.1 where it belongs.
@joepetjr

joepetjr commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Updated for the 0.1.1 release that landed on main (2a0f2fc) while this was open. Merged main in rather than rebasing — a rebase needs a force-push, which is blocked in this environment, and the merge leaves the branch strictly ahead so CI now tests the real merged content.

One thing the automatic merge got wrong, corrected by hand: it folded this branch's changelog entry into the ## 0.1.1 - 2026-08-03 section, which would have claimed cwd/permission_mode shipped in a release it is not in. Moved back under a fresh ## Unreleased heading above 0.1.1; the Windows-installer entry stays in 0.1.1 where it belongs.

Versions now match main exactly — no bump from this PR.

Separate pre-existing issue, not touched here: on main, pyproject.toml reads 0.1.1 but claude_batch_runner/__init__.py still reads __version__ = "0.1.0" — the release commit bumped one and not the other. Left alone to keep this PR to one concern; worth its own fix.

73 tests pass on the merged content, and the consuming runner's gate re-probed green against it: (True, 'pinnable via cwd + permission_mode').

@joepetjr
joepetjr merged commit 383e032 into main Aug 3, 2026
3 checks passed
@joepetjr
joepetjr deleted the feat/call-agent-cwd-and-permission-mode-rebased branch August 3, 2026 23:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant