diff --git a/CHANGELOG.internal.md b/CHANGELOG.internal.md index df21575ae..59dde3a5c 100644 --- a/CHANGELOG.internal.md +++ b/CHANGELOG.internal.md @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 74fc4417c..ecb8b362a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/f1/test-drives/2026-03-02-cypack-868-codex-sandbox-defaults-validation.md b/apps/f1/test-drives/2026-03-02-cypack-868-codex-sandbox-defaults-validation.md new file mode 100644 index 000000000..100427aeb --- /dev/null +++ b/apps/f1/test-drives/2026-03-02-cypack-868-codex-sandbox-defaults-validation.md @@ -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. diff --git a/packages/codex-runner/src/CodexRunner.ts b/packages/codex-runner/src/CodexRunner.ts index 2ec9273e7..35c1206de 100644 --- a/packages/codex-runner/src/CodexRunner.ts +++ b/packages/codex-runner/src/CodexRunner.ts @@ -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; @@ -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 diff --git a/packages/codex-runner/test/CodexRunner.mcp-config.test.ts b/packages/codex-runner/test/CodexRunner.mcp-config.test.ts index 14f2e431b..69d7b18b4 100644 --- a/packages/codex-runner/test/CodexRunner.mcp-config.test.ts +++ b/packages/codex-runner/test/CodexRunner.mcp-config.test.ts @@ -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"); + }); });