From 5d85d63c7481b4b32e49a0c5d5b41157ccd29f9b Mon Sep 17 00:00:00 2001 From: NagyVikt Date: Mon, 11 May 2026 12:52:55 +0200 Subject: [PATCH] Make Guardex Codex lanes approval-free by default Guardex launches Codex inside sandbox worktrees, but approval prompts still interrupt lane completion unless every caller remembers to pass -a never. The launcher now injects that conservative approval policy by default while preserving the Codex sandbox and respecting explicit caller approval flags. The conflict-review relaunch path uses the same argument builder so takeover/retry sessions behave consistently. Constraint: User asked for a durable fix instead of per-session flags Rejected: Use --dangerously-bypass-approvals-and-sandbox by default | it would also drop sandboxing, which is broader than needed Confidence: high Scope-risk: narrow Directive: Do not remove explicit-flag detection unless Codex CLI flag compatibility is rechecked Tested: bash -n scripts/codex-agent.sh templates/scripts/codex-agent.sh Tested: node --test test/sandbox.test.js Tested: openspec validate agent-codex-codex-task-2026-05-11-12-44 --type change --strict Tested: openspec validate --specs Not-tested: node --test test/metadata.test.js remains red on unrelated Cosign installer pin expectation --- .../.openspec.yaml | 2 ++ .../proposal.md | 17 ++++++++++ .../specs/codex-task/spec.md | 16 +++++++++ .../tasks.md | 34 +++++++++++++++++++ templates/scripts/codex-agent.sh | 30 ++++++++++++++-- test/sandbox.test.js | 5 ++- 6 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 openspec/changes/agent-codex-codex-task-2026-05-11-12-44/.openspec.yaml create mode 100644 openspec/changes/agent-codex-codex-task-2026-05-11-12-44/proposal.md create mode 100644 openspec/changes/agent-codex-codex-task-2026-05-11-12-44/specs/codex-task/spec.md create mode 100644 openspec/changes/agent-codex-codex-task-2026-05-11-12-44/tasks.md diff --git a/openspec/changes/agent-codex-codex-task-2026-05-11-12-44/.openspec.yaml b/openspec/changes/agent-codex-codex-task-2026-05-11-12-44/.openspec.yaml new file mode 100644 index 0000000..81cd71f --- /dev/null +++ b/openspec/changes/agent-codex-codex-task-2026-05-11-12-44/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-11 diff --git a/openspec/changes/agent-codex-codex-task-2026-05-11-12-44/proposal.md b/openspec/changes/agent-codex-codex-task-2026-05-11-12-44/proposal.md new file mode 100644 index 0000000..488960f --- /dev/null +++ b/openspec/changes/agent-codex-codex-task-2026-05-11-12-44/proposal.md @@ -0,0 +1,17 @@ +## Why + +- Guardex-launched Codex sessions still prompt for approval in the middle of agent work. +- The intended default is durable approval-free execution for Guardex Codex lanes without requiring a per-session `codex -a never` wrapper. + +## What Changes + +- `scripts/codex-agent.sh` injects `-a never` into Codex CLI launches by default. +- Explicit caller approval flags still win, and `GUARDEX_CODEX_APPROVAL_POLICY` can override or disable the injected policy. +- Conflict-review Codex relaunches use the same approval policy behavior. +- Focused sandbox tests cover the default and explicit override paths. + +## Impact + +- Affects only Guardex Codex launcher sessions. +- Keeps the Codex filesystem/network sandbox unchanged; this removes approval prompts, not sandboxing. +- The local `scripts/codex-agent.sh` symlink and managed template share the same implementation. diff --git a/openspec/changes/agent-codex-codex-task-2026-05-11-12-44/specs/codex-task/spec.md b/openspec/changes/agent-codex-codex-task-2026-05-11-12-44/specs/codex-task/spec.md new file mode 100644 index 0000000..9ba219d --- /dev/null +++ b/openspec/changes/agent-codex-codex-task-2026-05-11-12-44/specs/codex-task/spec.md @@ -0,0 +1,16 @@ +## ADDED Requirements + +### Requirement: Guardex Codex sessions default to no approval prompts +Guardex Codex launcher sessions SHALL pass an approval policy of `never` to Codex unless the caller supplied an approval policy explicitly or disabled the default. + +#### Scenario: Default launcher invocation +- **WHEN** `scripts/codex-agent.sh` launches Codex without an approval policy argument +- **THEN** Codex is invoked with `-a never`. + +#### Scenario: Explicit caller override +- **WHEN** `scripts/codex-agent.sh` receives an approval policy argument from the caller +- **THEN** the launcher SHALL NOT add a second default `-a never` argument. + +#### Scenario: Conflict review relaunch +- **WHEN** the launcher starts a Codex conflict-review pass +- **THEN** the same default approval policy handling applies. diff --git a/openspec/changes/agent-codex-codex-task-2026-05-11-12-44/tasks.md b/openspec/changes/agent-codex-codex-task-2026-05-11-12-44/tasks.md new file mode 100644 index 0000000..1f02460 --- /dev/null +++ b/openspec/changes/agent-codex-codex-task-2026-05-11-12-44/tasks.md @@ -0,0 +1,34 @@ +## Definition of Done + +This change is complete only when **all** of the following are true: + +- Every checkbox below is checked. +- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff. +- If any step blocks (test failure, conflict, ambiguous result), append a `BLOCKED:` line under section 4 explaining the blocker and **STOP**. Do not tick remaining cleanup boxes; do not silently skip the cleanup pipeline. + +## Handoff + +- Handoff: change=`agent-codex-codex-task-2026-05-11-12-44`; branch=`agent//`; scope=`TODO`; action=`continue this sandbox or finish cleanup after a usage-limit/manual takeover`. +- Copy prompt: Continue `agent-codex-codex-task-2026-05-11-12-44` on branch `agent//`. Work inside the existing sandbox, review `openspec/changes/agent-codex-codex-task-2026-05-11-12-44/tasks.md`, continue from the current state instead of creating a new sandbox, and when the work is done run `gx branch finish --branch agent// --base dev --via-pr --wait-for-merge --cleanup`. + +## 1. Specification + +- [x] 1.1 Finalize proposal scope and acceptance criteria for `agent-codex-codex-task-2026-05-11-12-44`. +- [x] 1.2 Define normative requirements in `specs/codex-task/spec.md`. + +## 2. Implementation + +- [x] 2.1 Implement scoped behavior changes. +- [x] 2.2 Add/update focused regression coverage. + +## 3. Verification + +- [x] 3.1 Run targeted project verification commands. +- [x] 3.2 Run `openspec validate agent-codex-codex-task-2026-05-11-12-44 --type change --strict`. +- [x] 3.3 Run `openspec validate --specs`. + +## 4. Cleanup (mandatory; run before claiming completion) + +- [ ] 4.1 Run the cleanup pipeline: `gx branch finish --branch agent// --base dev --via-pr --wait-for-merge --cleanup`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation. +- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff. +- [ ] 4.3 Confirm the sandbox worktree is gone (`git worktree list` no longer shows the agent path; `git branch -a` shows no surviving local/remote refs for the branch). diff --git a/templates/scripts/codex-agent.sh b/templates/scripts/codex-agent.sh index 683bf36..8968770 100755 --- a/templates/scripts/codex-agent.sh +++ b/templates/scripts/codex-agent.sh @@ -6,6 +6,7 @@ AGENT_NAME="${GUARDEX_AGENT_NAME:-agent}" BASE_BRANCH="${GUARDEX_BASE_BRANCH:-}" BASE_BRANCH_EXPLICIT=0 CODEX_BIN="${GUARDEX_CODEX_BIN:-codex}" +CODEX_APPROVAL_POLICY="${GUARDEX_CODEX_APPROVAL_POLICY-never}" NODE_BIN="${GUARDEX_NODE_BIN:-node}" CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}" AUTO_FINISH_RAW="${GUARDEX_CODEX_AUTO_FINISH:-true}" @@ -67,6 +68,29 @@ normalize_tier() { esac } +codex_args_include_approval_policy() { + local arg + for arg in "$@"; do + case "$arg" in + -a|--ask-for-approval|--approval-policy|--dangerously-bypass-approvals-and-sandbox) + return 0 + ;; + --ask-for-approval=*|--approval-policy=*) + return 0 + ;; + esac + done + return 1 +} + +build_codex_launch_args() { + CODEX_LAUNCH_ARGS=() + if [[ -n "$CODEX_APPROVAL_POLICY" ]] && ! codex_args_include_approval_policy "$@"; then + CODEX_LAUNCH_ARGS+=("-a" "$CODEX_APPROVAL_POLICY") + fi + CODEX_LAUNCH_ARGS+=("$@") +} + string_contains_any() { local haystack="$1" shift @@ -1039,7 +1063,8 @@ run_finish_flow() { ( cd "$wt" set +e - "$CODEX_BIN" "$review_prompt" + build_codex_launch_args "$review_prompt" + "$CODEX_BIN" "${CODEX_LAUNCH_ARGS[@]}" review_exit="$?" set -e if [[ "$review_exit" -ne 0 ]]; then @@ -1097,10 +1122,11 @@ trap cleanup_active_session_state_on_exit EXIT INT TERM echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path" cd "$worktree_path" set +e +build_codex_launch_args "$@" GUARDEX_TASK_MODE="$TASK_MODE" \ GUARDEX_OPENSPEC_TIER="$OPENSPEC_TIER" \ GUARDEX_TASK_ROUTING_REASON="$TASK_ROUTING_REASON" \ - "$CODEX_BIN" "$@" + "$CODEX_BIN" "${CODEX_LAUNCH_ARGS[@]}" codex_exit="$?" set -e diff --git a/test/sandbox.test.js b/test/sandbox.test.js index ddbbdae..bab6533 100644 --- a/test/sandbox.test.js +++ b/test/sandbox.test.js @@ -106,6 +106,7 @@ test('codex-agent launches codex inside a fresh sandbox worktree and keeps branc ); const launchedArgs = fs.readFileSync(argsMarker, 'utf8').trim(); + assert.match(launchedArgs, /-a never/); assert.match(launchedArgs, /--model gpt-5\.4-mini/); assert.equal(fs.existsSync(launchedCwd), true, 'clean codex-agent sandbox should stay available by default'); @@ -397,7 +398,7 @@ test('codex-agent supports --codex-bin override before positional arguments', () const cwdMarker = path.join(repoDir, '.codex-agent-cwd-override'); const argsMarker = path.join(repoDir, '.codex-agent-args-override'); const launch = runCodexAgent( - ['--codex-bin', fakeCodexPath, 'launch-task', 'planner', 'dev', '--model', 'gpt-5.4-mini'], + ['--codex-bin', fakeCodexPath, 'launch-task', 'planner', 'dev', '-a', 'on-request', '--model', 'gpt-5.4-mini'], repoDir, { GUARDEX_TEST_CODEX_CWD: cwdMarker, @@ -414,6 +415,8 @@ test('codex-agent supports --codex-bin override before positional arguments', () new RegExp(`${escapeRegexLiteral(repoDir)}/\\.omx/agent-worktrees/${escapeRegexLiteral(path.basename(repoDir))}__planner__`), ); const launchedArgs = fs.readFileSync(argsMarker, 'utf8').trim(); + assert.match(launchedArgs, /-a on-request/); + assert.doesNotMatch(launchedArgs, /-a never/); assert.match(launchedArgs, /--model gpt-5\.4-mini/); assert.equal(fs.existsSync(launchedCwd), true, 'override invocation should keep sandbox unless cleanup is requested'); });