Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ This changelog documents internal development changes, refactors, tooling update

## [Unreleased]

### Fixed
- `CodexRunner` now respects platform sandbox defaults by only setting `threadOptions.sandboxMode` when `config.sandbox` is explicitly provided, and by no longer injecting implicit `configOverrides.sandbox_workspace_write` defaults. Added tests for explicit/implicit sandbox behavior. ([CYPACK-868](https://linear.app/ceedar/issue/CYPACK-868), [#922](https://github.com/ceedaragents/cyrus/pull/922))

## [0.2.25] - 2026-02-27

### Fixed
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Fixed
- **Codex sandbox defaults now follow platform configuration** - Codex-runner sessions no longer force a `workspace-write` sandbox or auto-enable sandbox network access when no sandbox settings are provided, so platform sandbox defaults are respected unless explicitly overridden. ([CYPACK-868](https://linear.app/ceedar/issue/CYPACK-868), [#922](https://github.com/ceedaragents/cyrus/pull/922))

## [0.2.26] - 2026-02-28 ([#918](https://github.com/ceedaragents/cyrus/pull/918))

### Changed
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Test Drive: CYPACK-868 Codex Sandbox Defaults Validation

**Date**: 2026-03-02
**Goal**: Validate Cyrus F1 end-to-end flow during Codex runner sandbox-default changes.
**Test Repo**: /tmp/f1-test-drive-cypack-868-20260302-172142

## Verification Results

### Issue-Tracker
- [x] Issue created
- [x] Issue ID returned
- [x] Issue metadata accessible

### EdgeWorker
- [x] Session started
- [x] Worktree created
- [x] Activities tracked
- [x] Agent processed issue (analysis phase started)

### Renderer
- [x] Activity format correct
- [x] Pagination works
- [x] Search works

## Session Log

1. `./f1 init-test-repo -p /tmp/f1-test-drive-cypack-868-20260302-172142`
- Result: pass; repo scaffolded and initial git commit created.

2. `HOME=/tmp CYRUS_PORT=3600 CYRUS_REPO_PATH=/tmp/f1-test-drive-cypack-868-20260302-172142 pnpm --filter cyrus-f1 run server`
- Result: pass; server started and RPC endpoint available at `http://localhost:3600/cli/rpc`.

3. `CYRUS_PORT=3600 ./f1 ping` and `CYRUS_PORT=3600 ./f1 status`
- Result: pass; server health and status returned.

4. `CYRUS_PORT=3600 ./f1 create-issue -t "CYPACK-868 validation clean run" -d "Validate F1 protocol execution."`
- Result: pass; created `issue-1` / `DEF-1`.

5. `CYRUS_PORT=3600 ./f1 start-session -i issue-1`
- Result: pass; started `session-1`.

6. `CYRUS_PORT=3600 ./f1 view-session -s session-1`
- Result: pass; activities visible with timestamp/type/message columns.

7. `CYRUS_PORT=3600 ./f1 view-session -s session-1 --limit 10 --offset 0`
- Result: pass; pagination output returned successfully.

8. `CYRUS_PORT=3600 ./f1 view-session -s session-1 --search "Analyzing"`
- Result: pass; filtered activity search returned matching entry.

9. `CYRUS_PORT=3600 ./f1 stop-session -s session-1`
- Result: pass; session stop acknowledged.

10. Server shutdown (`Ctrl+C`)
- Result: pass; graceful shutdown completed.

## Final Retrospective

- Core F1 flow (repo init, server startup, issue creation, session creation, activity rendering, pagination, search, stop, shutdown) worked as expected.
- This drive provides end-to-end validation evidence for the testing protocol requirement.
- Initial run without `HOME=/tmp` reproduced known environment-specific `EPERM` writes under `~/.claude/debug`; rerun with `HOME=/tmp` completed cleanly.
21 changes: 1 addition & 20 deletions packages/codex-runner/src/CodexRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,13 +573,13 @@ export class CodexRunner extends EventEmitter implements IAgentRunner {

const threadOptions: ThreadOptions = {
model: this.config.model,
sandboxMode: this.config.sandbox || "workspace-write",
workingDirectory: this.config.workingDirectory,
skipGitRepoCheck: this.config.skipGitRepoCheck ?? true,
approvalPolicy: this.config.askForApproval || "never",
...(reasoningEffort ? { modelReasoningEffort: reasoningEffort } : {}),
...(webSearchMode ? { webSearchMode } : {}),
...(additionalDirectories.length > 0 ? { additionalDirectories } : {}),
...(this.config.sandbox ? { sandboxMode: this.config.sandbox } : {}),
};

return threadOptions;
Expand Down Expand Up @@ -759,25 +759,6 @@ export class CodexRunner extends EventEmitter implements IAgentRunner {
}
}

const sandboxWorkspaceWrite = configOverrides.sandbox_workspace_write;
// Keep workspace-write as the default sandbox, but enable outbound network so
// common remote workflows (for example `git`/`gh` against GitHub) work without
// requiring danger-full-access.
if (
sandboxWorkspaceWrite &&
typeof sandboxWorkspaceWrite === "object" &&
!Array.isArray(sandboxWorkspaceWrite)
) {
configOverrides.sandbox_workspace_write = {
...sandboxWorkspaceWrite,
network_access:
(sandboxWorkspaceWrite as { network_access?: boolean })
.network_access ?? true,
};
} else if (!sandboxWorkspaceWrite) {
configOverrides.sandbox_workspace_write = { network_access: true };
}

if (!appendSystemPrompt) {
return Object.keys(configOverrides).length > 0
? configOverrides
Expand Down
54 changes: 54 additions & 0 deletions packages/codex-runner/test/CodexRunner.mcp-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,58 @@ describe("CodexRunner MCP config mapping", () => {
});
expect(mcpServers.linear.bearer_token_env_var).toBe("LINEAR_API_TOKEN");
});

it("does not inject sandbox workspace-write config when none is provided", () => {
const runner = new CodexRunner({
workingDirectory: process.cwd(),
});

const configOverrides = (runner as any).buildConfigOverrides();
expect(configOverrides).toBeUndefined();
});

it("preserves explicit sandbox workspace-write config without forcing network_access", () => {
const runner = new CodexRunner({
workingDirectory: process.cwd(),
configOverrides: {
sandbox_workspace_write: {
read_only_access: {
network: true,
include_tmpdir: true,
},
},
},
});

const configOverrides = (runner as any).buildConfigOverrides();
expect(configOverrides).toEqual({
sandbox_workspace_write: {
read_only_access: {
network: true,
include_tmpdir: true,
},
},
});
});

it("does not default sandbox mode when sandbox is not configured", () => {
const runner = new CodexRunner({
workingDirectory: process.cwd(),
model: "gpt-5",
});

const threadOptions = (runner as any).buildThreadOptions();
expect(threadOptions.sandboxMode).toBeUndefined();
});

it("passes sandbox mode when explicitly configured", () => {
const runner = new CodexRunner({
workingDirectory: process.cwd(),
model: "gpt-5",
sandbox: "workspace-write",
});

const threadOptions = (runner as any).buildThreadOptions();
expect(threadOptions.sandboxMode).toBe("workspace-write");
});
});
Loading