Skip to content

Commit 0b50a56

Browse files
NagyViktNagyVikt
andauthored
Make Guardex Codex lanes approval-free by default (#554)
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 Co-authored-by: NagyVikt <nagy.viktordp@gmail.com>
1 parent 74e4489 commit 0b50a56

6 files changed

Lines changed: 101 additions & 3 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-05-11
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## Why
2+
3+
- Guardex-launched Codex sessions still prompt for approval in the middle of agent work.
4+
- The intended default is durable approval-free execution for Guardex Codex lanes without requiring a per-session `codex -a never` wrapper.
5+
6+
## What Changes
7+
8+
- `scripts/codex-agent.sh` injects `-a never` into Codex CLI launches by default.
9+
- Explicit caller approval flags still win, and `GUARDEX_CODEX_APPROVAL_POLICY` can override or disable the injected policy.
10+
- Conflict-review Codex relaunches use the same approval policy behavior.
11+
- Focused sandbox tests cover the default and explicit override paths.
12+
13+
## Impact
14+
15+
- Affects only Guardex Codex launcher sessions.
16+
- Keeps the Codex filesystem/network sandbox unchanged; this removes approval prompts, not sandboxing.
17+
- The local `scripts/codex-agent.sh` symlink and managed template share the same implementation.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
## ADDED Requirements
2+
3+
### Requirement: Guardex Codex sessions default to no approval prompts
4+
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.
5+
6+
#### Scenario: Default launcher invocation
7+
- **WHEN** `scripts/codex-agent.sh` launches Codex without an approval policy argument
8+
- **THEN** Codex is invoked with `-a never`.
9+
10+
#### Scenario: Explicit caller override
11+
- **WHEN** `scripts/codex-agent.sh` receives an approval policy argument from the caller
12+
- **THEN** the launcher SHALL NOT add a second default `-a never` argument.
13+
14+
#### Scenario: Conflict review relaunch
15+
- **WHEN** the launcher starts a Codex conflict-review pass
16+
- **THEN** the same default approval policy handling applies.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
## Definition of Done
2+
3+
This change is complete only when **all** of the following are true:
4+
5+
- Every checkbox below is checked.
6+
- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff.
7+
- 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.
8+
9+
## Handoff
10+
11+
- Handoff: change=`agent-codex-codex-task-2026-05-11-12-44`; branch=`agent/<your-name>/<branch-slug>`; scope=`TODO`; action=`continue this sandbox or finish cleanup after a usage-limit/manual takeover`.
12+
- Copy prompt: Continue `agent-codex-codex-task-2026-05-11-12-44` on branch `agent/<your-name>/<branch-slug>`. 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/<your-name>/<branch-slug> --base dev --via-pr --wait-for-merge --cleanup`.
13+
14+
## 1. Specification
15+
16+
- [x] 1.1 Finalize proposal scope and acceptance criteria for `agent-codex-codex-task-2026-05-11-12-44`.
17+
- [x] 1.2 Define normative requirements in `specs/codex-task/spec.md`.
18+
19+
## 2. Implementation
20+
21+
- [x] 2.1 Implement scoped behavior changes.
22+
- [x] 2.2 Add/update focused regression coverage.
23+
24+
## 3. Verification
25+
26+
- [x] 3.1 Run targeted project verification commands.
27+
- [x] 3.2 Run `openspec validate agent-codex-codex-task-2026-05-11-12-44 --type change --strict`.
28+
- [x] 3.3 Run `openspec validate --specs`.
29+
30+
## 4. Cleanup (mandatory; run before claiming completion)
31+
32+
- [ ] 4.1 Run the cleanup pipeline: `gx branch finish --branch agent/<your-name>/<branch-slug> --base dev --via-pr --wait-for-merge --cleanup`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation.
33+
- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff.
34+
- [ ] 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).

templates/scripts/codex-agent.sh

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ AGENT_NAME="${GUARDEX_AGENT_NAME:-agent}"
66
BASE_BRANCH="${GUARDEX_BASE_BRANCH:-}"
77
BASE_BRANCH_EXPLICIT=0
88
CODEX_BIN="${GUARDEX_CODEX_BIN:-codex}"
9+
CODEX_APPROVAL_POLICY="${GUARDEX_CODEX_APPROVAL_POLICY-never}"
910
NODE_BIN="${GUARDEX_NODE_BIN:-node}"
1011
CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
1112
AUTO_FINISH_RAW="${GUARDEX_CODEX_AUTO_FINISH:-true}"
@@ -67,6 +68,29 @@ normalize_tier() {
6768
esac
6869
}
6970

71+
codex_args_include_approval_policy() {
72+
local arg
73+
for arg in "$@"; do
74+
case "$arg" in
75+
-a|--ask-for-approval|--approval-policy|--dangerously-bypass-approvals-and-sandbox)
76+
return 0
77+
;;
78+
--ask-for-approval=*|--approval-policy=*)
79+
return 0
80+
;;
81+
esac
82+
done
83+
return 1
84+
}
85+
86+
build_codex_launch_args() {
87+
CODEX_LAUNCH_ARGS=()
88+
if [[ -n "$CODEX_APPROVAL_POLICY" ]] && ! codex_args_include_approval_policy "$@"; then
89+
CODEX_LAUNCH_ARGS+=("-a" "$CODEX_APPROVAL_POLICY")
90+
fi
91+
CODEX_LAUNCH_ARGS+=("$@")
92+
}
93+
7094
string_contains_any() {
7195
local haystack="$1"
7296
shift
@@ -1039,7 +1063,8 @@ run_finish_flow() {
10391063
(
10401064
cd "$wt"
10411065
set +e
1042-
"$CODEX_BIN" "$review_prompt"
1066+
build_codex_launch_args "$review_prompt"
1067+
"$CODEX_BIN" "${CODEX_LAUNCH_ARGS[@]}"
10431068
review_exit="$?"
10441069
set -e
10451070
if [[ "$review_exit" -ne 0 ]]; then
@@ -1097,10 +1122,11 @@ trap cleanup_active_session_state_on_exit EXIT INT TERM
10971122
echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
10981123
cd "$worktree_path"
10991124
set +e
1125+
build_codex_launch_args "$@"
11001126
GUARDEX_TASK_MODE="$TASK_MODE" \
11011127
GUARDEX_OPENSPEC_TIER="$OPENSPEC_TIER" \
11021128
GUARDEX_TASK_ROUTING_REASON="$TASK_ROUTING_REASON" \
1103-
"$CODEX_BIN" "$@"
1129+
"$CODEX_BIN" "${CODEX_LAUNCH_ARGS[@]}"
11041130
codex_exit="$?"
11051131
set -e
11061132

test/sandbox.test.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ test('codex-agent launches codex inside a fresh sandbox worktree and keeps branc
106106
);
107107

108108
const launchedArgs = fs.readFileSync(argsMarker, 'utf8').trim();
109+
assert.match(launchedArgs, /-a never/);
109110
assert.match(launchedArgs, /--model gpt-5\.4-mini/);
110111

111112
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', ()
397398
const cwdMarker = path.join(repoDir, '.codex-agent-cwd-override');
398399
const argsMarker = path.join(repoDir, '.codex-agent-args-override');
399400
const launch = runCodexAgent(
400-
['--codex-bin', fakeCodexPath, 'launch-task', 'planner', 'dev', '--model', 'gpt-5.4-mini'],
401+
['--codex-bin', fakeCodexPath, 'launch-task', 'planner', 'dev', '-a', 'on-request', '--model', 'gpt-5.4-mini'],
401402
repoDir,
402403
{
403404
GUARDEX_TEST_CODEX_CWD: cwdMarker,
@@ -414,6 +415,8 @@ test('codex-agent supports --codex-bin override before positional arguments', ()
414415
new RegExp(`${escapeRegexLiteral(repoDir)}/\\.omx/agent-worktrees/${escapeRegexLiteral(path.basename(repoDir))}__planner__`),
415416
);
416417
const launchedArgs = fs.readFileSync(argsMarker, 'utf8').trim();
418+
assert.match(launchedArgs, /-a on-request/);
419+
assert.doesNotMatch(launchedArgs, /-a never/);
417420
assert.match(launchedArgs, /--model gpt-5\.4-mini/);
418421
assert.equal(fs.existsSync(launchedCwd), true, 'override invocation should keep sandbox unless cleanup is requested');
419422
});

0 commit comments

Comments
 (0)