fix(intake): honor explicit ATIF per-result tool status - #1265
Conversation
Intake derived tool-span status from a case-insensitive "[error]" substring search over result content, so a successful tool whose output quoted an unrelated error was stored with status "error" and had its successful payload copied into error_message. This was seen in a production qa-copilot trace where a glean_search result containing "[ERROR] Failed to download package" was recorded as a failure and counted toward the trace error_count. ATIF defines no normalized tool-result error field, and the existing step-level markers are gated on the step having a single observation result, so neither can express status for parallel tool calls. Consume AtifObservationResult.extra.is_error as an authoritative per-result signal in both directions, ahead of the existing step-level markers. The "[error]" text fallback is retained for producers that signal failure only in result text; it can be removed once those producers emit an explicit per-result status. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: morgan <morganc@nvidia.com>
📝 WalkthroughWalkthroughChangesThe tool-result mapper now prioritizes a boolean Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
services/intake/tests/test_atif_v17.py (1)
737-745: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest explicit
Falseagainst both legacy boolean branches.These tests only prove that
extra.is_error=Falseoverrides text. Add fixtures wheretool_result_metadata.is_error=Trueandtool_result_is_error=True. Both must produceSpanStatus.SUCCESS. A later branch reordering can otherwise break the authoritative-false contract without failing this suite.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@services/intake/tests/test_atif_v17.py` around lines 737 - 745, Extend test_atif_mapping_keeps_successful_tool_result_that_quotes_an_error with separate fixtures using tool_result_metadata.is_error=True and tool_result_is_error=True alongside extra.is_error=False. Assert each mapping produces SpanStatus.SUCCESS, no error_message, and successful statuses for all spans, preserving the authoritative explicit-false behavior across both legacy boolean branches.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@services/intake/tests/test_atif_v17.py`:
- Around line 737-745: Extend
test_atif_mapping_keeps_successful_tool_result_that_quotes_an_error with
separate fixtures using tool_result_metadata.is_error=True and
tool_result_is_error=True alongside extra.is_error=False. Assert each mapping
produces SpanStatus.SUCCESS, no error_message, and successful statuses for all
spans, preserving the authoritative explicit-false behavior across both legacy
boolean branches.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 15a30e5b-b548-4e68-9e48-8c9abc19803b
📒 Files selected for processing (2)
services/intake/src/nmp/intake/spans/ingest/atif_mapping.pyservices/intake/tests/test_atif_v17.py
|
Problem
Intake marks a successful ATIF tool call as failed when the tool's returned text contains the case-insensitive substring
[error]anywhere in the payload._tool_result_is_error()ended with:This treats arbitrary returned content as a status channel, which is unsafe for search, retrieval, log-reading, and ticket-reading tools — successful results routinely quote error messages from other systems.
Observed in a production
qa-copilottrace (efdefb9d-94e2-4051-9839-c5b9caef03ea): a successfulglean_searchreturned results containing the log line[ERROR] Failed to download package. The producer reported success (isError: false,"success": true), but Intake stored the tool span withstatus: "error"and copied the successful search response intoerror_message. Sincerepository/clickhouse/trace.pycomputescountIf(status = 'error') AS error_count, the span also inflated the trace's error count.Reproduced on
mainbefore the fix; the mapper producedstatus=errorwitherror_messageset to the full successful payload.Fix
Consume
AtifObservationResult.extra.is_erroras an authoritative per-result signal, in both directions, ahead of the existing markers. The field is already accepted by the schema but was never read.Per-result rather than step-level is deliberate:
_matched_tool_result_metadata()and_step_extra_bool()are both gated on_is_only_observation_result(), so neither can express status for parallel tool calls. That gap is why producers resorted to text sentinels. Note also that a step-levelis_error: falsewould not have helped — the existing branches only short-circuit on exactlyTrue, soFalsefell through to the substring check.The three existing branches are unchanged. Source diff is 13 lines.
Why the text fallback is retained
It cannot be safely removed yet:
success. Claude Code trajectories already send both step-level markers (seetests/integration/spans/test_atif_ingest.py), so they do not depend on it — but producers that signal secondary parallel-call failures only in text do, and the step-level markers structurally cannot express those.[ERROR]here is mid-line inside JSON, but would still misfire on log-reading tools where[ERROR]legitimately begins lines. That trades one silent wrong answer for a narrower one.The intended sequencing is: land this, migrate producers to emit
extra.is_error, then remove the fallback. Sanitizing or escaping[ERROR]in tool content is explicitly not proposed — it would alter source evidence and only move the ambiguity to another string.Producer contract
{ "source_call_id": "call-1", "content": "...", "extra": { "is_error": false } }Backward compatible: trajectories without
result.extra.is_errorcontinue through the existing paths unchanged.Tests
Six new tests in
services/intake/tests/test_atif_v17.py:extra.is_error: falsewith content containing[ERROR]produces a successful span with noerror_messageextra.is_error: trueproduces an error span even when content has no marker[error]content remains an error_subagent_ref_to_span) honors explicit status — this is a second call site of_tool_result_is_error(), so the false positive was not limited to tool spansVerification
test_atif_mapping_keeps_tool_error_on_tool_spanpre-commit run --filesclean on both changed files (ruff, ruff format, ty, copyright headers)services/intakesuite green on this branch: 455 passed, 0 skipped (~12 min). The integration tests self-provision ClickHouse and were included in that run.Validated against: https://docs.nvidia.com/nemo/relay/configure-plugins/observability/atif as a valid path

Summary by CodeRabbit
Bug Fixes
Tests