spacr.qt.ai.issue_report

Error reports filed as issues on the public spaCR GitHub repository.

A failed run is reported in one of two ways, chosen by the “One-click issue filing” preference (spacr.qt.preferences.get_issue_prompt_mode()):

  • 'always', the default: file_without_review() files the report as soon as the run has failed. It sends public_report(), the same redaction the preview applies by default, and it never opens a browser. Filing needs a GitHub sign-in (the gh CLI or GITHUB_TOKEN). Without one, nothing is sent.

  • 'ask': the report opens in an editable preview, and submit_report() sends it only after the Send click. Without a sign-in it opens a pre-filled GitHub form in the browser.

Both paths build the report with build_report(), and both look for an open issue carrying the same traceback fingerprint before they open a new one. spaCR never stores a durable GitHub token itself.

Functions

build_report(→ Dict[str, str])

Build a (title, body) pair for a pre-filled GitHub issue.

file_issue(→ str)

Legacy end-to-end helper retained for API callers and tests.

file_without_review(→ Dict[str, str])

File a report automatically, for issue reporting set to 'always'.

fingerprint_of(→ str)

The fingerprint build_report() gives this traceback.

issue_url(→ str)

Build the https://github.com/<repo>/issues/new?… URL.

log_bundle_dir(→ pathlib.Path)

Where a report's log copy is written.

log_tail(→ str)

Return the last n_lines of ~/.spacr/logs/spacr.log (or

open_issue_in_browser(→ bool)

Open url in the user's default browser.

public_report(→ Dict[str, str])

The report as it is sent when nobody reviews it first.

redact_identity(→ str)

Replace this computer's login and host names with placeholders.

redact_secrets(→ str)

Strip anything that looks like an API key / access token.

sanitize_path(→ str)

Replace absolute paths pointing inside $HOME with ~/.

sanitize_settings(→ Dict[str, Any])

Return a copy of settings with paths + DB names sanitized.

sanitize_traceback(→ str)

Sanitise a full traceback string via sanitize_path().

save_log_bundle(→ Optional[pathlib.Path])

Write the log tail to a file beside the report and return its path.

strip_report_paths(→ str)

Remove file/folder names from an already sanitised report.

submit_report(→ str)

Submit one payload the user has already approved in the preview.

Module Contents

spacr.qt.ai.issue_report.build_report(traceback_text: str, active_app: str = '', settings: Dict[str, Any] | None = None, include_log_tail: bool = True, ai_response: str = '') → Dict[str, str][source]

Build a (title, body) pair for a pre-filled GitHub issue.

Parameters:
  • traceback_text – full traceback text (as caught by traceback.format_exc()).

  • active_app – id of the app the user was in when the error fired ("mask" / "measure" / …).

  • settings – the pipeline settings dict in play, if any. Sanitised before inclusion.

  • include_log_tail – also attach the last N log lines.

  • ai_response – spaCR AI’s analysis of this same error, when the AI is switched on and has already answered. Sanitised and length-capped like everything else here, and clearly marked as machine-generated: it is a lead for whoever reads the report, not a finding.

Returns:

dict with keys title, body and fingerprint, ready to be URL-encoded onto issues/new.

spacr.qt.ai.issue_report.file_issue(traceback_text: str, active_app: str = '', settings: Dict[str, Any] | None = None, *, include_log_tail: bool = True, ai_response: str = '') → str[source]

Legacy end-to-end helper retained for API callers and tests.

The GUI does not call this directly: it builds the payload, displays an editable preview, then passes the approved mapping to submit_report(). Headless callers invoking this function are the report-specific affirmative action themselves.

Parameters:

traceback_text – full traceback text, passed to build_report(), which sanitises it and derives the issue title and fingerprint from it.

spacr.qt.ai.issue_report.file_without_review(report: Dict[str, str]) → Dict[str, str][source]

File a report automatically, for issue reporting set to ‘always’.

Runs on a worker thread: resolving the sign-in can run gh auth token, and posting waits on api.github.com.

It never opens a browser. The browser form is a prompt the user answers, and ‘always’ is the choice not to be prompted. So without a GitHub sign-in it files nothing and says so.

Parameters:

report – the payload to post, already passed through public_report().

Returns:

{"status": ..., "url": ..., "detail": ...} with a status of FILED, SEEN_AGAIN (a comment on the open issue with the same fingerprint), SIGNED_OUT, REFUSED (inside a test run) or FAILED. Never raises.

spacr.qt.ai.issue_report.fingerprint_of(traceback_text: str) → str[source]

The fingerprint build_report() gives this traceback.

Parameters:

traceback_text – the raw traceback.

Returns:

six hex characters, without building the report. The automatic filer checks this against what it has filed before, so a repeated crash costs no log copy and no network call.

spacr.qt.ai.issue_report.issue_url(title: str, body: str, label: str = ISSUE_LABEL, repo: str = REPO) → str[source]

Build the https://github.com/<repo>/issues/new?… URL.

The URL is truncated to ~7.5 KB so it fits GitHub’s parser limit; an ellipsis + note is appended to the body when we clip.

Parameters:
  • title – URL-encodable issue title.

  • body – markdown body; may be truncated.

  • label – label to attach (created lazily by GitHub if it doesn’t already exist).

  • repo – owner/name slug.

Returns:

fully-quoted https://github.com/… URL.

spacr.qt.ai.issue_report.log_bundle_dir() → pathlib.Path[source]

Where a report’s log copy is written.

spacr.qt.ai.issue_report.log_tail(n_lines: int = LOG_TAIL_LINES, log_path: pathlib.Path | None = None) → str[source]

Return the last n_lines of ~/.spacr/logs/spacr.log (or a custom path), sanitized.

Parameters:
  • n_lines – how many trailing lines to include.

  • log_path – override for the log file path.

Returns:

sanitised last-N-lines block or "" if the file is absent or unreadable.

spacr.qt.ai.issue_report.open_issue_in_browser(url: str) → bool[source]

Open url in the user’s default browser.

Parameters:

url – the address to open, typically from issue_url(); it is opened in a new tab where the browser supports one.

Returns:

True if webbrowser accepted the request, else False.

spacr.qt.ai.issue_report.public_report(report: Dict[str, str]) → Dict[str, str][source]

The report as it is sent when nobody reviews it first.

The preview opens with “Remove file and folder names” switched on, so what a reviewer sends by default is the body after strip_report_paths(). A report filed automatically gets that same body. The title is stripped as well: it quotes the exception line, and that line can carry a file name.

Parameters:

report – a report from build_report().

Returns:

title, body and fingerprint, ready to post.

spacr.qt.ai.issue_report.redact_identity(s: str) → str[source]

Replace this computer’s login and host names with placeholders.

A home folder is already shortened to ~ by sanitize_path(). The login name can still appear elsewhere: another folder named after the user, a permission error, an owner setting. The host name can appear in a network path or a connection error. Each is replaced wherever it stands as a whole word, in any letter case.

Parameters:

s – arbitrary text.

Returns:

the text with USER_PLACEHOLDER and HOST_PLACEHOLDER in place of those names.

spacr.qt.ai.issue_report.redact_secrets(s: str) → str[source]

Strip anything that looks like an API key / access token.

The issue body is posted to a PUBLIC GitHub repo, so a token that survived into a traceback, a settings value or a log line would be leaked to the world (and, for GitHub PATs, instantly revoked).

Parameters:

s – arbitrary text.

Returns:

the same text with credential-shaped substrings replaced by REDACTED.

spacr.qt.ai.issue_report.sanitize_path(s: str) → str[source]

Replace absolute paths pointing inside $HOME with ~/.

Also collapses any string that looks like an on-disk *.db path down to <DB> so lab / patient / experiment identifiers embedded in a filename don’t leak, replaces this computer’s login and host names through redact_identity(), and redacts credential-shaped substrings via redact_secrets().

Parameters:

s – arbitrary text.

Returns:

text with home-relative paths abbreviated and DB paths, login and host names and secrets redacted.

spacr.qt.ai.issue_report.sanitize_settings(settings: Dict[str, Any]) → Dict[str, Any][source]

Return a copy of settings with paths + DB names sanitized.

Values whose key names a credential (api_key, GITHUB_TOKEN, password, …) are dropped entirely — the key name is enough of a hint that the value must never reach a public issue.

Parameters:

settings – any pipeline settings dict.

Returns:

sanitized copy safe to include in a public issue.

spacr.qt.ai.issue_report.sanitize_traceback(tb: str) → str[source]

Sanitise a full traceback string via sanitize_path().

Parameters:

tb – the traceback text; None or an empty string gives "".

spacr.qt.ai.issue_report.save_log_bundle(fingerprint: str, log_path: pathlib.Path | None = None, n_lines: int = LOG_BUNDLE_LINES) → pathlib.Path | None[source]

Write the log tail to a file beside the report and return its path.

The public issue names this path instead of carrying the log itself. More lines are kept here than would ever have gone in an issue – once the log is not being pasted into a URL there is no length to stay under, and whoever reads the report wants the whole run, not a keyhole.

Parameters:
  • fingerprint – the traceback hash, so one report’s log is easy to match to the issue that names it.

  • log_path – override for the log file path.

  • n_lines – how many trailing lines to keep.

Returns:

the path written, or None if there was nothing to write or the write failed – a report must still be filable on a read-only home directory.

spacr.qt.ai.issue_report.strip_report_paths(text: str) → str[source]

Remove file/folder names from an already sanitised report.

The ordinary sanitizer abbreviates the home directory so a traceback is still useful. Public reports default to the stricter form: traceback file fields, quoted path values and remaining absolute path-like tokens become <PATH>. The preview lets the user restore the useful names before sending; with issue reporting set to ‘always’ there is no preview, so what this leaves behind is what gets published.

A PATH CAN CONTAIN A SPACE, and the rule that stopped one at the first whitespace published the rest of it. /Volumes/Lab Drive/Patient 042 came out as <PATH> Drive/Patient 042, and C:\Users\anna\OneDrive - Karolinska Institutet\Screens kept the institution and the screen — the two operating systems whose own folders have spaces in them. A UNC path was not a path at all: nothing here started at \\. The LAST component of an unquoted path has no separator after it to prove a space is inside the path rather than after it, so it runs on only over words shaped like a name – a number, a capitalised word, a file name – and \\LAB-NAS\screens\plate 1 is replaced whole while opening /mnt/data/x.tif failed keeps its failed. A lower-case word ending such a component still survives it; a quoted path is taken whole, and the title and every repr-ed settings value are quoted.

A slash straight after < starts a closing tag, not a path. The report’s collapsible sections end in </summary> and </details>, and issue #121 was filed with both turned into <<PATH>, so every section after the first one stayed open.

Parameters:

text – a report title or body, already through sanitize_path().

Returns:

the same text with path-shaped tokens replaced by <PATH>.

spacr.qt.ai.issue_report.submit_report(report: Dict[str, str]) → str[source]

Submit one payload the user has already approved in the preview.

Parameters:

report – the approved payload with title and body keys, plus fingerprint for the duplicate search when a GitHub sign-in posts it through the API. Without a sign-in, or when posting fails, the title and body are put into a pre-filled issue URL that is opened in the browser.

Returns:

the filed issue’s URL, the pre-filled issues/new URL, or the transport refusal message when network use is refused (inside a test run).