Skip to content

fix: persist scan data on exit and save partial reports#464

Open
octo-patch wants to merge 1 commit into
usestrix:mainfrom
octo-patch:fix/issue-294-save-report-on-exit
Open

fix: persist scan data on exit and save partial reports#464
octo-patch wants to merge 1 commit into
usestrix:mainfrom
octo-patch:fix/issue-294-save-report-on-exit

Conversation

@octo-patch
Copy link
Copy Markdown
Contributor

Fixes #294

Problem

Two related issues cause scan reports to be lost when Strix exits:

1. tracer.cleanup() never called in main.py's finally block

cli.py and tui.py register atexit + signal handlers that call tracer.cleanup(), but main.py's finally block only calls posthog.end(). On Windows (where asyncio.WindowsSelectorEventLoopPolicy() is used), the atexit handlers may not fire reliably when the asyncio event loop tears down, so vulnerability reports and the final scan summary are never written to disk.

2. Strict validation in finish_actions.py silently discards partial reports

finish_scan rejects any call where one of the four required fields is empty and returns an error without saving. When the LLM doesn't populate every field, the entire report is dropped — even though vulnerabilities were already saved incrementally via add_vulnerability_report().

Solution

Fix 1 (strix/interface/main.py): Call tracer.cleanup() in the finally block before posthog.end(). cleanup() calls save_run_data(mark_complete=True) which is idempotent (tracks already-saved vuln IDs and the _run_completed_emitted flag), so calling it from both the finally block and an atexit handler is safe.

Fix 2 (strix/tools/finish/finish_actions.py): Replace the all-or-nothing validation with a graceful fallback: missing or empty fields are filled with the placeholder "[Not provided by model]" so partial reports are saved rather than silently discarded.

Testing

  • Verified tracer.cleanup() is idempotent by reviewing _saved_vuln_ids set tracking and _run_completed_emitted flag in tracer.py.
  • Existing tests in tests/telemetry/test_tracer.py call save_run_data(mark_complete=True) multiple times, confirming the idempotency assumption holds.

…ix#294)

Two related fixes for reports not being saved when Strix exits:

1. Call tracer.cleanup() in main.py's finally block before posthog.end()
   so that run data is always written to disk regardless of whether the
   atexit handlers registered in cli.py/tui.py fire (they can be skipped
   on Windows when the asyncio event loop tears down).

2. Replace the strict all-or-nothing validation in finish_actions.py with
   a graceful fallback: missing fields get the placeholder
   "[Not provided by model]" so partial reports are saved rather than
   silently discarded when the LLM omits a section.

Co-Authored-By: Octopus <liyuan851277048@icloud.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 24, 2026

Greptile Summary

This PR fixes two related data-loss issues: it explicitly calls tracer.cleanup() in main.py's finally block (ensuring scan data is persisted on Windows where atexit handlers may not fire), and it replaces strict field validation in finish_scan with graceful fallback placeholders so partial reports are saved rather than silently discarded.

Confidence Score: 5/5

Safe to merge — changes are targeted, idempotency is well-guarded, and the only finding is a trivial redundant .strip() call.

Both changes are well-scoped: cleanup() is idempotent (guarded by _saved_vuln_ids and _run_completed_emitted), vulnerability_reports is not cleared so the post-finally exit-code check is unaffected, and the fallback placeholder logic in finish_actions.py is straightforward. The sole finding is a P2 style issue (redundant .strip()) that does not affect correctness.

No files require special attention.

Important Files Changed

Filename Overview
strix/interface/main.py Adds tracer.cleanup() in the finally block before posthog.end(). cleanup() calls save_run_data(mark_complete=True), which is idempotent (guarded by _saved_vuln_ids set and _run_completed_emitted flag), so double invocation via atexit is safe. vulnerability_reports is not cleared by cleanup(), so the post-finally sys.exit(2) check on line 640 is unaffected.
strix/tools/finish/finish_actions.py Replaces strict all-or-nothing validation with graceful fallback: empty/missing fields are filled with "[Not provided by model]". Minor style issue: .strip() is called redundantly a second time inside update_scan_final_fields() invocation (lines 114–117) after values were already stripped on lines 103–106.

Comments Outside Diff (1)

  1. strix/tools/finish/finish_actions.py, line 113-118 (link)

    P2 Redundant .strip() calls after normalization

    The values are already stripped (or set to _NOT_PROVIDED) on lines 103–106, so the four .strip() calls passed into tracer.update_scan_final_fields() are no-ops. This is harmless, but it creates a small inconsistency — future readers might wonder why normalization happens in two places.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: strix/tools/finish/finish_actions.py
    Line: 113-118
    
    Comment:
    **Redundant `.strip()` calls after normalization**
    
    The values are already stripped (or set to `_NOT_PROVIDED`) on lines 103–106, so the four `.strip()` calls passed into `tracer.update_scan_final_fields()` are no-ops. This is harmless, but it creates a small inconsistency — future readers might wonder why normalization happens in two places.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: strix/tools/finish/finish_actions.py
Line: 113-118

Comment:
**Redundant `.strip()` calls after normalization**

The values are already stripped (or set to `_NOT_PROVIDED`) on lines 103–106, so the four `.strip()` calls passed into `tracer.update_scan_final_fields()` are no-ops. This is harmless, but it creates a small inconsistency — future readers might wonder why normalization happens in two places.

```suggestion
        tracer.update_scan_final_fields(
            executive_summary=executive_summary,
            methodology=methodology,
            technical_analysis=technical_analysis,
            recommendations=recommendations,
        )
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: persist scan data on exit and save ..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Report generated during scan but not saved on exit (no .md file, exit with vulnerabilities to 0)

1 participant