commit d9c46bc912d7f7d9953a846bc243f53046055a34
parent 0094611f73eb8d61088e08a35eb9aa63fd451da0
Author: William Casarin <jb55@jb55.com>
Date: Tue, 24 Feb 2026 16:02:30 -0800
wip codex debugging
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
2 files changed, 137 insertions(+), 45 deletions(-)
diff --git a/crates/notedeck_dave/src/backend/codex.rs b/crates/notedeck_dave/src/backend/codex.rs
@@ -473,11 +473,18 @@ fn handle_codex_message(
let method = match &msg.method {
Some(m) => m.as_str(),
None => {
- // Response to a request we sent (e.g. approval ack). Nothing to do.
+ tracing::debug!("codex msg with no method (response): id={:?}", msg.id);
return HandleResult::Continue;
}
};
+ tracing::debug!(
+ "codex msg: method={} id={:?} has_params={}",
+ method,
+ msg.id,
+ msg.params.is_some()
+ );
+
match method {
"item/agentMessage/delta" => {
if let Some(params) = msg.params {
@@ -521,54 +528,94 @@ fn handle_codex_message(
}
"item/commandExecution/requestApproval" => {
+ tracing::info!(
+ "CMD APPROVAL: id={:?} has_params={}",
+ msg.id,
+ msg.params.is_some()
+ );
if let (Some(rpc_id), Some(params)) = (msg.id, msg.params) {
- if let Ok(approval) = serde_json::from_value::<CommandApprovalParams>(params) {
- return check_approval_or_forward(
- rpc_id,
- "Bash",
- serde_json::json!({ "command": approval.command }),
- response_tx,
- ctx,
- );
+ tracing::info!(
+ "CMD APPROVAL params: {}",
+ serde_json::to_string(¶ms).unwrap_or_default()
+ );
+ match serde_json::from_value::<CommandApprovalParams>(params) {
+ Ok(approval) => {
+ let cmd = approval.command_string();
+ tracing::info!("CMD APPROVAL deserialized ok: command={}", cmd);
+ return check_approval_or_forward(
+ rpc_id,
+ "Bash",
+ serde_json::json!({ "command": cmd }),
+ response_tx,
+ ctx,
+ );
+ }
+ Err(e) => {
+ tracing::error!("CMD APPROVAL deser FAILED: {}", e);
+ }
}
+ } else {
+ tracing::warn!("CMD APPROVAL missing id or params");
}
}
"item/fileChange/requestApproval" => {
+ tracing::info!(
+ "FILE APPROVAL: id={:?} has_params={}",
+ msg.id,
+ msg.params.is_some()
+ );
if let (Some(rpc_id), Some(params)) = (msg.id, msg.params) {
- if let Ok(approval) = serde_json::from_value::<FileChangeApprovalParams>(params) {
- let kind_str = approval
- .kind
- .as_ref()
- .and_then(|k| k.get("type").and_then(|t| t.as_str()))
- .unwrap_or("edit");
-
- let (tool_name, tool_input) = match kind_str {
- "create" => (
- "Write",
- serde_json::json!({
- "file_path": approval.file_path,
- "content": approval.diff.as_deref().unwrap_or(""),
- }),
- ),
- _ => (
- "Edit",
- serde_json::json!({
- "file_path": approval.file_path,
- "old_string": "",
- "new_string": approval.diff.as_deref().unwrap_or(""),
- }),
- ),
- };
+ tracing::info!(
+ "FILE APPROVAL params: {}",
+ serde_json::to_string(¶ms).unwrap_or_default()
+ );
+ match serde_json::from_value::<FileChangeApprovalParams>(params) {
+ Ok(approval) => {
+ let file_path = approval.file_path.as_deref().unwrap_or("unknown");
+ let kind_str = approval
+ .kind
+ .as_ref()
+ .and_then(|k| k.get("type").and_then(|t| t.as_str()))
+ .unwrap_or("edit");
+
+ let (tool_name, tool_input) = match kind_str {
+ "create" => (
+ "Write",
+ serde_json::json!({
+ "file_path": file_path,
+ "content": approval.diff.as_deref().unwrap_or(""),
+ }),
+ ),
+ _ => (
+ "Edit",
+ serde_json::json!({
+ "file_path": file_path,
+ "old_string": "",
+ "new_string": approval.diff.as_deref().unwrap_or(""),
+ }),
+ ),
+ };
- return check_approval_or_forward(
- rpc_id,
- tool_name,
- tool_input,
- response_tx,
- ctx,
- );
+ tracing::info!(
+ "FILE APPROVAL deserialized ok: tool={} file={}",
+ tool_name,
+ file_path
+ );
+ return check_approval_or_forward(
+ rpc_id,
+ tool_name,
+ tool_input,
+ response_tx,
+ ctx,
+ );
+ }
+ Err(e) => {
+ tracing::error!("FILE APPROVAL deser FAILED: {}", e);
+ }
}
+ } else {
+ tracing::warn!("FILE APPROVAL missing id or params");
}
}
@@ -592,7 +639,15 @@ fn handle_codex_message(
}
other => {
- tracing::debug!("Unhandled codex notification: {}", other);
+ tracing::debug!(
+ "Unhandled codex notification: {} id={:?} params={}",
+ other,
+ msg.id,
+ msg.params
+ .as_ref()
+ .map(|p| serde_json::to_string(p).unwrap_or_default())
+ .unwrap_or_default()
+ );
}
}
diff --git a/crates/notedeck_dave/src/backend/codex_protocol.rs b/crates/notedeck_dave/src/backend/codex_protocol.rs
@@ -191,22 +191,59 @@ pub struct ItemCompletedParams {
pub content: Option<String>,
}
-/// `item/commandExecution/requestApproval` params — server asks client to approve a command
+/// `item/commandExecution/requestApproval` params — server asks client to approve a command.
+///
+/// In V2, `command` and `cwd` are optional. When absent the command can be
+/// reconstructed from `proposed_execpolicy_amendment` (an argv vec).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommandApprovalParams {
- pub command: String,
+ /// The command string (present in V2 when the Command presentation is used).
+ #[serde(default)]
+ pub command: Option<String>,
#[serde(default)]
pub cwd: Option<String>,
+ /// Reason the approval is needed (e.g. "Write outside workspace").
+ #[serde(default)]
+ pub reason: Option<String>,
+ /// Proposed execpolicy amendment — an argv array that can serve as a
+ /// fallback when `command` is absent.
+ #[serde(default)]
+ pub proposed_execpolicy_amendment: Option<Vec<String>>,
+}
+
+impl CommandApprovalParams {
+ /// Best-effort command string for display / auto-accept evaluation.
+ pub fn command_string(&self) -> String {
+ if let Some(ref cmd) = self.command {
+ return cmd.clone();
+ }
+ if let Some(ref argv) = self.proposed_execpolicy_amendment {
+ return argv.join(" ");
+ }
+ "unknown command".to_string()
+ }
}
-/// `item/fileChange/requestApproval` params — server asks client to approve a file change
+/// `item/fileChange/requestApproval` params — server asks client to approve a file change.
+///
+/// In V2, the params are minimal (just item/thread/turn ids + reason).
+/// `file_path`, `diff`, and `kind` may be absent.
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileChangeApprovalParams {
- pub file_path: String,
+ #[serde(default)]
+ pub file_path: Option<String>,
+ #[serde(default)]
pub diff: Option<String>,
+ #[serde(default)]
pub kind: Option<Value>,
+ /// Reason the approval is needed.
+ #[serde(default)]
+ pub reason: Option<String>,
+ /// Root directory the agent is requesting write access for.
+ #[serde(default)]
+ pub grant_root: Option<String>,
}
/// `turn/completed` params