Describe the bug
When create_vulnerability_report refuses to file a report, the TUI renders it as a complete, successfully-filed vulnerability report. The operator gets no indication that nothing was saved.
CreateVulnerabilityReportRenderer.render (strix/interface/tui/renderers/reporting_renderer.py:138) reads only severity and cvss_score out of result. Every other field it prints comes from args — what the model asked to file — so a rejected call still produces a full, well-formed report body. It then hard-codes the status:
css_classes = cls.get_css_classes("completed")
return Static(padded, classes=css_classes)
overriding the "failed" status that live_view._tool_status_from_result correctly computed.
The sibling renderer in the same file already handles exactly this. CreateDependencyReportRenderer.render (:320) opens with:
if isinstance(result, dict) and (result.get("success") is False or result.get("warning")):
return cls._render_unsuccessful(args, result)
and both tools return the same payload shapes — create_vulnerability_report produces them at strix/tools/reporting/tool.py:228 (validation failed), :241 (persisted-state unavailable warning), :266 (dedupe rejection) and :303 (exception), mirroring create_dependency_report at :758, :783, :801, :832.
The three rejection paths are all routine during a scan. The dedupe one in particular fires whenever the agent re-reports a finding:
return {
"success": False,
"error": (
f"Potential duplicate of '{duplicate_title}' "
f"(id={duplicate_id[:8]}...) — do not re-report the same vulnerability"
),
...
}
To Reproduce
- Run a scan where the agent files a vulnerability, then files a near-identical one (or one that fails field validation).
- Watch the TUI.
- The second call renders as a full
🐞 Vulnerability Report with title, target, PoC and remediation, styled as completed.
- The finding is absent from
vulnerabilities.json, the final report and the SARIF export.
Directly, without a scan:
from strix.interface.tui.renderers.reporting_renderer import CreateVulnerabilityReportRenderer
widget = CreateVulnerabilityReportRenderer.render({
"args": {"title": "SQL injection in /api/login", "remediation_steps": "Use parameterized queries."},
"result": {"success": False, "error": "Potential duplicate of 'SQLi in login' (id=8f2ab1c0...)"},
"status": "failed",
})
print(widget.content.plain) # full report body, no mention of the rejection
print(widget.classes) # {'status-completed', ...}
Expected behavior
The rejection is shown, the way the dependency renderer shows it: ✗ Not created: <error>, with status-failed.
Actual behavior
A full success-styled report body, status-completed, and no mention of the error.
Note that .tool-call.status-failed and .tool-call.status-completed are visually identical in strix/interface/assets/tui_styles.tcss:378-394, so even the correct status class would not currently signal anything on its own.
System Information:
- OS: macOS 15.3
- Strix Version or Commit:
48b4821 (v1.2.0)
- Python Version: 3.12
- LLM Used: any — reproducible by calling the renderer directly
Additional context
I have a PR that adds the same guard the dependency renderer has, sharing one helper between the two so they cannot drift apart again, plus tests/test_reporting_renderer.py covering all three rejection payloads.
Describe the bug
When
create_vulnerability_reportrefuses to file a report, the TUI renders it as a complete, successfully-filed vulnerability report. The operator gets no indication that nothing was saved.CreateVulnerabilityReportRenderer.render(strix/interface/tui/renderers/reporting_renderer.py:138) reads onlyseverityandcvss_scoreout ofresult. Every other field it prints comes fromargs— what the model asked to file — so a rejected call still produces a full, well-formed report body. It then hard-codes the status:overriding the
"failed"status thatlive_view._tool_status_from_resultcorrectly computed.The sibling renderer in the same file already handles exactly this.
CreateDependencyReportRenderer.render(:320) opens with:and both tools return the same payload shapes —
create_vulnerability_reportproduces them atstrix/tools/reporting/tool.py:228(validation failed),:241(persisted-state unavailable warning),:266(dedupe rejection) and:303(exception), mirroringcreate_dependency_reportat:758,:783,:801,:832.The three rejection paths are all routine during a scan. The dedupe one in particular fires whenever the agent re-reports a finding:
To Reproduce
🐞 Vulnerability Reportwith title, target, PoC and remediation, styled as completed.vulnerabilities.json, the final report and the SARIF export.Directly, without a scan:
Expected behavior
The rejection is shown, the way the dependency renderer shows it:
✗ Not created: <error>, withstatus-failed.Actual behavior
A full success-styled report body,
status-completed, and no mention of the error.Note that
.tool-call.status-failedand.tool-call.status-completedare visually identical instrix/interface/assets/tui_styles.tcss:378-394, so even the correct status class would not currently signal anything on its own.System Information:
48b4821(v1.2.0)Additional context
I have a PR that adds the same guard the dependency renderer has, sharing one helper between the two so they cannot drift apart again, plus
tests/test_reporting_renderer.pycovering all three rejection payloads.