Repository navigation
fix: unwrap flare-gateway calls before completion-gate classification #653
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,8 +72,17 @@ fn item_action_succeeded(action: &str, response: Option<&Value>) -> Option<bool> | |
| fn parse_post_tool_use(input: &str) -> Option<PostToolUseInput> { | ||
| let v: serde_json::Value = serde_json::from_str(input).ok()?; | ||
| let session_id = v.get("session_id")?.as_str()?.to_string(); | ||
| let tool_name = v.get("tool_name")?.as_str()?.to_string(); | ||
| let tool_input = v.get("tool_input"); | ||
| let raw_tool_name = v.get("tool_name")?.as_str()?.to_string(); | ||
| let raw_tool_input = v.get("tool_input").cloned(); | ||
| // Unwrap flare-gateway `action="execute"` calls (item #559) -- a | ||
| // verification command run via `mcp__flare__tool(action="execute", | ||
| // server="leanctx", tool="ctx_shell", args={"command": ...})` must be | ||
| // recorded exactly like a direct Bash call, or the completion gate | ||
| // never sees it as evidence and rejects `item done`/`check_merge` even | ||
| // right after a passing run. | ||
| let (tool_name, tool_input) = | ||
| crate::hook_redirect::unwrap_gateway_call(&raw_tool_name, raw_tool_input.as_ref()); | ||
| let tool_input = tool_input.as_ref(); | ||
| let command = tool_input | ||
| .and_then(|ti| { | ||
| ti.get("command") | ||
|
|
@@ -99,11 +108,26 @@ fn parse_post_tool_use(input: &str) -> Option<PostToolUseInput> { | |
| .map(|r| { | ||
| let stdout = r.get("stdout").and_then(Value::as_str).unwrap_or(""); | ||
| let stderr = r.get("stderr").and_then(Value::as_str).unwrap_or(""); | ||
| if stdout.is_empty() && stderr.is_empty() { | ||
| r.as_str().unwrap_or_default().to_string() | ||
| } else { | ||
| format!("{stdout}\n{stderr}") | ||
| if !stdout.is_empty() || !stderr.is_empty() { | ||
| return format!("{stdout}\n{stderr}"); | ||
| } | ||
| if let Some(s) = r.as_str() { | ||
| return s.to_string(); | ||
| } | ||
| // A gateway-forwarded response (item #559) comes back as the | ||
| // MCP content-block envelope (`{"content":[{"type":"text", | ||
| // "text":"..."}]}`), same shape `response_as_json` unwraps for | ||
| // item actions -- without this, exit_code and output_text are | ||
| // both blank for every gateway-routed verification command, so | ||
| // a genuine failure would default to "passed" via | ||
| // `verification_passed`'s permissive fallback below. | ||
| r.get("content") | ||
| .and_then(Value::as_array) | ||
| .and_then(|blocks| blocks.first()) | ||
| .and_then(|block| block.get("text")) | ||
| .and_then(Value::as_str) | ||
| .unwrap_or_default() | ||
| .to_string() | ||
|
Comment on lines
+124
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🤖 get_repo_knowledge executed:
Length of output: 2032 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- changed file diff ---'
git diff -- src/hook_completion_gate.rs
printf '%s\n' '--- hook completion gate focused source ---'
cat -n src/hook_completion_gate.rs | sed -n '1,180p'
printf '%s\n' '--- init matcher focused source ---'
cat -n src/init.rs | sed -n '260,335p'
printf '%s\n' '--- relevant declarations and tests ---'
rg -n -C 3 'verification_passed|isError|content|hook_completion_gate|PostToolUse|CallToolResult' src Cargo.toml Cargo.lock tests 2>/dev/null | head -n 400Repository: getappz/agentflare Length of output: 39946 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- completion-gate call sites and handler ---'
rg -n -C 8 'verification_passed|parse_post_tool_use|item_success|PostToolUseInput|read_stdin_or_skip|completion_gate' src/hook_completion_gate.rs src/hook.rs src/hook_redirect.rs
printf '%s\n' '--- gateway implementation ---'
cat -n src/hook_redirect.rs | sed -n '200,275p'
printf '%s\n' '--- completion-gate tests ---'
rg -n -C 12 '#\[cfg\(test\)\]|#\[test\]|verification_passed|content.*text|tool_response|post_tool_use' src/hook_completion_gate.rs
printf '%s\n' '--- dependency declarations ---'
rg -n -C 3 'rmcp|model-context|mcp' Cargo.toml Cargo.lockRepository: getappz/agentflare Length of output: 50374 🌐 Web query:
💡 Result: In the Model Context Protocol (MCP), the CallToolResult is the structure returned by a server in response to a tools/call request [1][2]. As of the June 18, 2025, specification, the CallToolResult interface is defined with the following key fields related to your query: content: ContentBlock[] This field contains an array of content objects (unstructured data) representing the result of the tool call [1][3]. Even when a tool call fails, this array should contain the error information (e.g., text explaining the failure) that allows the language model to understand and potentially correct the error [4][2][3]. isError: boolean (optional) This field indicates whether the tool execution was successful [1][3]. - If set to true, it signifies that the tool call resulted in an error (e.g., API failure, invalid input, or business logic error) [5][4][3]. - If not set or set to false, it is assumed the call was successful [1][2][3]. - Errors originating from the tool should be reported via this field rather than as JSON-RPC protocol-level errors, ensuring the LLM can see the error details [1][4][3]. For complete technical specifications, you can refer to the official MCP schema definitions, such as the schema file from June 18, 2025 [1][3]. Citations:
Treat MCP tool errors as failed verification and scan every text block.
🤖 Prompt for AI Agents |
||
| }) | ||
| .unwrap_or_default(); | ||
| let item_success = item_action | ||
|
|
@@ -351,6 +375,35 @@ mod tests { | |
| assert!(parse_post_tool_use("not json").is_none()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn parse_post_tool_use_unwraps_gateway_routed_verification_command() { | ||
| // Regression for item #559: a verification command run through the | ||
| // flare gateway (`mcp__flare__tool(action="execute", server= | ||
| // "leanctx", tool="ctx_shell", args={"command": ...})`) must be | ||
| // recognized exactly like a direct Bash call. | ||
| let input = r#"{"session_id":"s1","tool_name":"mcp__flare__tool","tool_input":{"action":"execute","server":"leanctx","tool":"ctx_shell","args":{"command":"cargo test"}},"tool_response":{"content":[{"type":"text","text":"test result: ok"}]}}"#; | ||
| let parsed = parse_post_tool_use(input).unwrap(); | ||
| assert_eq!(parsed.tool_name, "mcp__lean-ctx__ctx_shell"); | ||
| assert_eq!(parsed.command.as_deref(), Some("cargo test")); | ||
| assert_eq!(parsed.output_text, "test result: ok"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn parse_post_tool_use_gateway_search_action_has_no_command() { | ||
| let input = r#"{"session_id":"s1","tool_name":"mcp__flare__tool","tool_input":{"action":"search","query":"ctx_shell"},"tool_response":{}}"#; | ||
| let parsed = parse_post_tool_use(input).unwrap(); | ||
| assert_eq!(parsed.tool_name, "mcp__flare__tool"); | ||
| assert!(parsed.command.is_none()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn output_text_falls_back_to_mcp_content_block_envelope() { | ||
| let input = r#"{"session_id":"s1","tool_name":"Bash","tool_input":{"command":"cargo test"},"tool_response":{"content":[{"type":"text","text":"3 failures:\n test_foo"}]}}"#; | ||
| let parsed = parse_post_tool_use(input).unwrap(); | ||
| assert_eq!(parsed.output_text, "3 failures:\n test_foo"); | ||
| assert!(!verification_passed(parsed.exit_code, &parsed.output_text)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn verification_passed_trusts_zero_exit_code() { | ||
| assert!(verification_passed(Some(0), "")); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Register the raw gateway tool for
PostToolUse.src/init.rs:295-315matches Bash-family names,item, and direct names fromMUTATING_TOOLS. The host evaluates that matcher against rawmcp__flare__toolbefore this parser can unwrap it.Therefore, gateway-routed
ctx_shellverification is not recorded. Gateway-routedctx_patchandctx_editalso do not invalidate prior verification evidence. Addmcp__flare__toolto the PostToolUse matcher and add an end-to-end dispatch test.🤖 Prompt for AI Agents