fix(agent): retrait --ask-for-approval invalide pour codex exec 0.137 — §17.9 D3
`codex exec` 0.137 ne connaît pas `--ask-for-approval` (`error: unexpected argument`) ; ce flag appartient à la commande interactive `codex`, pas à la sous-commande `exec` déjà non-interactive. Sa présence cassait toute réponse d'un agent Codex (flux structuré terminé sans Final), bloquant la conversation inter-agents. argv désormais verrouillé exact dans les tests (16/16 verts). Découvert et corrigé pendant la validation réelle de la messagerie inter-agents. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -141,19 +141,20 @@ impl CodexExecSession {
|
|||||||
|
|
||||||
/// Compose la ligne de commande d'un tour.
|
/// Compose la ligne de commande d'un tour.
|
||||||
///
|
///
|
||||||
/// Format RÉEL vérifié 2026-06-09 :
|
/// Format RÉEL vérifié 2026-06-10 (codex 0.137.0) :
|
||||||
/// - Conversation neuve : `codex exec --json --skip-git-repo-check
|
/// - Conversation neuve : `codex exec --json --skip-git-repo-check
|
||||||
/// --sandbox workspace-write --ask-for-approval never <prompt>`.
|
/// --sandbox workspace-write <prompt>`.
|
||||||
/// - Reprise (id connu) : `codex exec resume <thread_id> --json
|
/// - Reprise (id connu) : `codex exec resume <thread_id> --json
|
||||||
/// --skip-git-repo-check --sandbox workspace-write --ask-for-approval never
|
/// --skip-git-repo-check --sandbox workspace-write <prompt>`.
|
||||||
/// <prompt>`.
|
|
||||||
///
|
///
|
||||||
/// **Autonomie d'écriture (D3)** : `--sandbox workspace-write` autorise l'agent à
|
/// **Autonomie d'écriture (D3)** : `--sandbox workspace-write` autorise l'agent à
|
||||||
/// écrire dans son workspace et `--ask-for-approval never` évite tout blocage sur
|
/// écrire dans son workspace. `codex exec` est déjà non-interactif (aucun prompt
|
||||||
/// une demande d'approbation interactive (un tour structuré non-interactif ne peut
|
/// d'approbation possible), donc on ne passe **pas** `--ask-for-approval` : ce flag
|
||||||
/// répondre à un prompt). Défaut raisonnable, aligné sur l'autonomie projet
|
/// appartient à la commande interactive `codex`, pas à la sous-commande `exec` qui
|
||||||
/// (CLAUDE.md §12) ; à terme **piloté par les permissions de l'agent**
|
/// sort sur `error: unexpected argument '--ask-for-approval' found`. Défaut
|
||||||
/// (`.ideai/permissions.json` + sandbox OS) — non implémenté ici.
|
/// raisonnable, aligné sur l'autonomie projet (CLAUDE.md §12) ; à terme **piloté par
|
||||||
|
/// les permissions de l'agent** (`.ideai/permissions.json` + sandbox OS) — non
|
||||||
|
/// implémenté ici.
|
||||||
fn build_spawn_line(&self, prompt: &str) -> SpawnLine {
|
fn build_spawn_line(&self, prompt: &str) -> SpawnLine {
|
||||||
let mut args = vec!["exec".to_owned()];
|
let mut args = vec!["exec".to_owned()];
|
||||||
if let Some(id) = self.conversation_id.lock().expect("mutex sain").as_ref() {
|
if let Some(id) = self.conversation_id.lock().expect("mutex sain").as_ref() {
|
||||||
@ -164,8 +165,6 @@ impl CodexExecSession {
|
|||||||
args.push("--skip-git-repo-check".to_owned());
|
args.push("--skip-git-repo-check".to_owned());
|
||||||
args.push("--sandbox".to_owned());
|
args.push("--sandbox".to_owned());
|
||||||
args.push("workspace-write".to_owned());
|
args.push("workspace-write".to_owned());
|
||||||
args.push("--ask-for-approval".to_owned());
|
|
||||||
args.push("never".to_owned());
|
|
||||||
args.push(prompt.to_owned());
|
args.push(prompt.to_owned());
|
||||||
SpawnLine {
|
SpawnLine {
|
||||||
command: self.command.clone(),
|
command: self.command.clone(),
|
||||||
|
|||||||
@ -1166,18 +1166,21 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// DURCISSEMENT QA (lot D3, §17.9 D3) — autonomie d'écriture Codex :
|
// DURCISSEMENT QA (lot D3, §17.9 D3 — fix codex 0.137) — autonomie
|
||||||
// la commande générée porte `--sandbox workspace-write` ET
|
// d'écriture Codex : la commande générée porte EXACTEMENT
|
||||||
// `--ask-for-approval never`, conversation NEUVE comme en REPRISE.
|
// [exec, --json, --skip-git-repo-check, --sandbox, workspace-write, <prompt>]
|
||||||
|
// (resume <id> en tête pour une reprise). Le flag `--ask-for-approval never`
|
||||||
|
// a été RETIRÉ : `codex exec` 0.137 ne le connaît pas (`error: unexpected
|
||||||
|
// argument`) et est déjà non-interactif. Ce test verrouille l'argv exact pour
|
||||||
|
// qu'aucune régression ne réintroduise un flag inconnu de la sous-commande.
|
||||||
// Prouvé via le sidecar argv du fake enregistreur (jamais le vrai codex).
|
// Prouvé via le sidecar argv du fake enregistreur (jamais le vrai codex).
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
|
|
||||||
/// Conversation NEUVE : `codex exec --json --skip-git-repo-check
|
/// Conversation NEUVE : argv EXACT `[exec, --json, --skip-git-repo-check,
|
||||||
/// --sandbox workspace-write --ask-for-approval never <prompt>`. On prouve la
|
/// --sandbox, workspace-write, <prompt>]`. Pas de sous-commande `resume`,
|
||||||
/// présence des deux flags d'autonomie ET leur appariement valeur (sandbox=
|
/// pas de `--ask-for-approval`.
|
||||||
/// workspace-write, ask-for-approval=never), sans sous-commande `resume`.
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn codex_new_conversation_command_carries_autonomy_flags() {
|
async fn codex_new_conversation_command_carries_exact_args() {
|
||||||
let (cmd, argv) = make_recording_fake(&[
|
let (cmd, argv) = make_recording_fake(&[
|
||||||
r#"{"type":"thread.started","thread_id":"cx-new"}"#,
|
r#"{"type":"thread.started","thread_id":"cx-new"}"#,
|
||||||
r#"{"type":"item.completed","item":{"id":"i0","type":"agent_message","text":"ok"}}"#,
|
r#"{"type":"item.completed","item":{"id":"i0","type":"agent_message","text":"ok"}}"#,
|
||||||
@ -1187,42 +1190,27 @@ mod tests {
|
|||||||
let recorded = std::fs::read_to_string(&argv).expect("argv");
|
let recorded = std::fs::read_to_string(&argv).expect("argv");
|
||||||
let args: Vec<&str> = recorded.lines().collect();
|
let args: Vec<&str> = recorded.lines().collect();
|
||||||
|
|
||||||
// Conversation neuve : pas de sous-commande resume.
|
|
||||||
assert!(
|
|
||||||
!args.contains(&"resume"),
|
|
||||||
"1er tour NEUF ne doit PAS porter resume, vu: {args:?}"
|
|
||||||
);
|
|
||||||
// Flags d'autonomie (D3), avec leur valeur appariée.
|
|
||||||
assert!(args.contains(&"--sandbox"), "vu: {args:?}");
|
|
||||||
assert!(args.contains(&"workspace-write"), "vu: {args:?}");
|
|
||||||
assert!(args.contains(&"--ask-for-approval"), "vu: {args:?}");
|
|
||||||
assert!(args.contains(&"never"), "vu: {args:?}");
|
|
||||||
// `--sandbox` est immédiatement suivi de `workspace-write`.
|
|
||||||
let sb = args.iter().position(|a| *a == "--sandbox").unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
args.get(sb + 1),
|
args,
|
||||||
Some(&"workspace-write"),
|
vec![
|
||||||
"--sandbox doit être suivi de workspace-write, vu: {args:?}"
|
"exec",
|
||||||
|
"--json",
|
||||||
|
"--skip-git-repo-check",
|
||||||
|
"--sandbox",
|
||||||
|
"workspace-write",
|
||||||
|
"salut",
|
||||||
|
],
|
||||||
|
"argv neuf doit être exact (sans resume, sans --ask-for-approval), vu: {args:?}"
|
||||||
);
|
);
|
||||||
// `--ask-for-approval` est immédiatement suivi de `never`.
|
|
||||||
let af = args.iter().position(|a| *a == "--ask-for-approval").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
args.get(af + 1),
|
|
||||||
Some(&"never"),
|
|
||||||
"--ask-for-approval doit être suivi de never, vu: {args:?}"
|
|
||||||
);
|
|
||||||
// La base + le prompt restent présents.
|
|
||||||
assert!(args.contains(&"--json") && args.contains(&"--skip-git-repo-check"));
|
|
||||||
assert!(args.contains(&"salut"), "vu: {args:?}");
|
|
||||||
let _ = std::fs::remove_file(&cmd);
|
let _ = std::fs::remove_file(&cmd);
|
||||||
let _ = std::fs::remove_file(&argv);
|
let _ = std::fs::remove_file(&argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// REPRISE (seed d'id) : `codex exec resume <id> --json --skip-git-repo-check
|
/// REPRISE (seed d'id) : argv EXACT `[exec, resume, <id>, --json,
|
||||||
/// --sandbox workspace-write --ask-for-approval never <prompt>`. Les flags
|
/// --skip-git-repo-check, --sandbox, workspace-write, <prompt>]`. Toujours
|
||||||
/// d'autonomie sont AUSSI présents lors d'une reprise.
|
/// pas de `--ask-for-approval`.
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn codex_resume_command_carries_autonomy_flags() {
|
async fn codex_resume_command_carries_exact_args() {
|
||||||
let (cmd, argv) = make_recording_fake(&[
|
let (cmd, argv) = make_recording_fake(&[
|
||||||
r#"{"type":"item.completed","item":{"id":"i0","type":"agent_message","text":"ok"}}"#,
|
r#"{"type":"item.completed","item":{"id":"i0","type":"agent_message","text":"ok"}}"#,
|
||||||
]);
|
]);
|
||||||
@ -1236,20 +1224,20 @@ mod tests {
|
|||||||
let recorded = std::fs::read_to_string(&argv).expect("argv");
|
let recorded = std::fs::read_to_string(&argv).expect("argv");
|
||||||
let args: Vec<&str> = recorded.lines().collect();
|
let args: Vec<&str> = recorded.lines().collect();
|
||||||
|
|
||||||
// Reprise : sous-commande resume + id.
|
assert_eq!(
|
||||||
assert!(args.contains(&"resume"), "vu: {args:?}");
|
args,
|
||||||
assert!(args.contains(&"cx-id"), "vu: {args:?}");
|
vec![
|
||||||
// Flags d'autonomie présents même en reprise.
|
"exec",
|
||||||
let sb = args
|
"resume",
|
||||||
.iter()
|
"cx-id",
|
||||||
.position(|a| *a == "--sandbox")
|
"--json",
|
||||||
.expect("--sandbox présent en reprise");
|
"--skip-git-repo-check",
|
||||||
assert_eq!(args.get(sb + 1), Some(&"workspace-write"), "vu: {args:?}");
|
"--sandbox",
|
||||||
let af = args
|
"workspace-write",
|
||||||
.iter()
|
"vas-y",
|
||||||
.position(|a| *a == "--ask-for-approval")
|
],
|
||||||
.expect("--ask-for-approval présent en reprise");
|
"argv reprise doit être exact (resume <id> en tête, sans --ask-for-approval), vu: {args:?}"
|
||||||
assert_eq!(args.get(af + 1), Some(&"never"), "vu: {args:?}");
|
);
|
||||||
let _ = std::fs::remove_file(&cmd);
|
let _ = std::fs::remove_file(&cmd);
|
||||||
let _ = std::fs::remove_file(&argv);
|
let _ = std::fs::remove_file(&argv);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user