spacr.crashreport

Create an attachable diagnostic bundle with spacr-crashreport.

This module produces one .zip with the logs, settings, versions and diagnostics a bug report needs, for attaching to a GitHub issue, and it is assembled out of the pieces spaCR already keeps rather than collected a second time:

  • spacr.doctor runs 17 checks over the installation — the running checkout, duplicate installs, the GPU, the Cellpose version, the project database, the settings — and every non-PASS row already carries the fix.

  • spacr.runctx writes a per-run JSONL log keyed by run id, so “show me everything from the run that produced this” has an answer. The bundle carries that run’s log verbatim, not a filtered summary of it.

  • spacr.errors stamps each run’s status onto the artifact it wrote, so the report can say whether the last run finished — which is a different question from whether it crashed just now, and the more useful one when a user reports numbers rather than a traceback.

Design rules:

Collectors do not raise. Every collector runs through _collect(), which turns a failure into a manifest entry naming what could not be gathered and why. Catching Exception is acceptable here because the failure is recorded in the output: the manifest is part of the bundle, so a missing section is visible to the report recipient instead of silently absent.

Bounded size. spacr.log rotates but a single run can still write hundreds of megabytes. The bundle takes the tail of it, capped by MAX_LOG_BYTES, and the manifest records how much was dropped.

Nothing secret. Environment variables are included because SPACR_*, CUDA_* and PATH explain a large fraction of “works here, not there” — but a value whose name looks like a credential is replaced with "<redacted>" and listed by name in the manifest, so you can see what was withheld and the report recipient can see that something was. Absolute paths are kept: they name the checkout, the plate and the database, and a report with them stripped cannot be acted on.

Deterministic layout. File names and ordering remain stable so repeated reports are quick to inspect.

Anonymous reports

The bundle above stays on this computer. What may leave it is a separate, much smaller thing: public_summary(), an allowlisted record of the versions, the operating-system family, the GPU class, the module, the exception type, a scrubbed one-line message and the call stack as file basenames, function names and line numbers. No log, setting, environment variable or path is in it. submit_public_summary() posts it to spaCR’s relay (REPORT_RELAY_URL), which files it as a public issue on EinarOlafsson/spacr, so a user needs no GitHub account. The switch is anonymous_reports_enabled(), on unless the user turns it off; with it off, or with NO_NETWORK_ENV set, nothing is sent. A report that cannot be sent waits in a small queue (QUEUE_LIMIT reports, QUEUE_RETENTION_S seconds) and is tried again later. diagnose_remotely() sends the same summary to the relay’s hosted model for advice.

Usage

python -m spacr.crashreport                     # last run, current folder
python -m spacr.crashreport --run-id 3f9c1a2b   # one particular run
python -m spacr.crashreport --db plate1/measurements/measurements.db \
    --settings measure.csv --app measure -o ~/spacr-bug.zip
from spacr.crashreport import report_exception, install_excepthook

install_excepthook()          # any unhandled crash writes a bundle
try:
    measure_crop(settings)
except Exception as exc:      # noqa: BLE001 - reporting, then re-raising
    print(report_exception(exc, settings=settings))
    raise

Exceptions

RelayRefused

The relay did not answer a hosted diagnosis.

Classes

CrashReport

Collected report sections and manifest, before serialisation.

Functions

ai_summary_prompt(→ str)

The fixed prompt that asks the user's own AI to summarise a report.

anonymous_report_notice_due(→ bool)

Whether the one-time notice about anonymised reports is still to show.

anonymous_reports_enabled(→ bool)

Whether any failure is sent as an anonymised report.

build_parser(→ argparse.ArgumentParser)

Return the spacr-crashreport argument parser.

classify_failure(→ str)

Return whether a failure is a fault or a user error.

collect(→ CrashReport)

Collect the crash-report sections into a CrashReport.

diagnose_remotely(→ Dict[str, str])

Ask the relay's hosted model about an error, with no account.

error_report_mode(→ str)

Return the "AI Error report" choice, a key of ERROR_REPORT_MODES.

example_public_summary(→ Dict[str, Any])

Build a summary from a made-up error, to show what a report holds.

explanation_key(→ str)

explanation_signature() of a failure, without the environment.

explanation_signature(→ str)

The error's signature, computed as the relay computes it.

fetch_allowance(→ Optional[Dict[str, Any]])

Ask the relay how much of today's free allowance this install has left.

find_last_run_id(→ str)

Return the id of the most recent run this machine logged.

flush_report_queue(→ Dict[str, int])

Try to send every waiting report, oldest first.

install_excepthook(→ Callable)

Write a crash report on any unhandled exception, then behave as before.

install_id(→ str)

Return this profile's random install id, creating it on first use.

main(→ int)

Command-line entry point. python -m spacr.crashreport.

mark_anonymous_report_notice_shown(→ None)

Record that the notice about anonymised reports has been shown.

mode_code_context(→ bool)

Whether a choice adds each spaCR frame's source line to a report.

mode_explains(→ bool)

Whether a choice has the AI explain every failure in the console.

mode_sends(→ bool)

Whether a choice files a failure of kind as an issue.

network_allowed(→ bool)

Whether a report or a hosted diagnosis may use the network at all.

next_reset_utc(→ datetime.datetime)

Return the next 00:00 UTC, when the relay's daily allowances reset.

normalise_cause(→ str)

A likely-cause key as the relay keeps it: kebab-case, 5 words, 40 chars.

normalise_question(→ str)

A question as both caches compare it.

public_environment(→ Dict[str, Any])

The computer and libraries a report was made on, without any name.

public_input_facts(→ Dict[str, Any])

What the run's source folder holds, without any name in it.

public_log_tail(→ str)

The console's last lines before a failure, scrubbed and cut.

public_problem_lines(→ List[str])

Scrub settings problems into short lines that may be published.

public_settings(→ Dict[str, Any])

The settings a run used, with nothing personal left in them.

public_summary(→ Dict[str, Any])

Build the anonymised summary of an error that may be published.

question_key(→ str)

The key a question's saved answer is kept under, or "".

queued_summaries(→ List[Dict[str, Any]])

Return the reports waiting to be sent, oldest first.

relay_url(→ str)

Return the relay address reports are posted to, without a trailing slash.

report_exception(→ str)

Write a crash report about exception and return where it went.

save_explanation(→ None)

Keep an explanation on this computer for the next identical error.

saved_explanation(→ Optional[Dict[str, Any]])

The explanation saved on this computer for an error, if still good.

scrub_public_lines(→ str)

Scrub a text like scrub_public_text(), keeping its line breaks.

scrub_public_text(→ str)

Remove anything personal from a short text that will be published.

set_anonymous_reports_enabled(→ None)

Turn reports on or off, as the first-run notice's switch does.

set_error_report_mode(→ None)

Save the "AI Error report" choice.

split_cause(→ Tuple[str, str])

Take the CAUSE: <slug> line off an AI answer.

submit_public_summary(→ Dict[str, Any])

Send an anonymised summary to the relay, or queue it for later.

submit_public_summary_in_background(...)

Queue a summary now and send it on a daemon thread.

write_crash_report(→ str)

Gather a report and write it as one zip.

Module Contents

exception spacr.crashreport.RelayRefused(reason: str, detail: str = '', scope: str = '', resets_at: datetime.datetime | None = None)[source]

Bases: RuntimeError

The relay did not answer a hosted diagnosis.

Parameters:
  • reason – "quota" when a free daily limit is used up, "network" when the relay could not be reached or the network is forbidden, and "error" for any other refusal.

  • detail – what the relay or the network said, for the log.

  • scope – for a quota refusal, whose limit it was: "install" (this install’s questions), "global" (spaCR’s shared free capacity) or "ip".

  • resets_at – when the limit resets, as an aware UTC datetime; defaults to the next 00:00 UTC.

The values are kept on the exception as reason, scope and resets_at.

Keep the reason, scope and reset time, and the detail as the message.

class spacr.crashreport.CrashReport[source]

Collected report sections and manifest, before serialisation.

Kept as data rather than written straight to a zip so that a caller — a test, the Qt layer, a support script — can inspect or re-render it without a temporary file, and so that write_crash_report() has nothing in it but serialisation.

Parameters:
  • created_utc – when the report was made, ISO-8601.

  • run_id – the run it is about, empty when none could be identified.

  • sections – file name to text, exactly as it will appear in the zip.

  • manifest – what was collected, what was not, and why. Every entry that failed carries the exception text; every entry that was truncated carries the byte counts.

summary() → str[source]

One short block naming the run, the versions and what is missing.

This is also summary.txt, the first file in the bundle, because a report recipient opening a zip should not have to guess which file to read first.

Returns:

the summary text.

property omitted: List[str][source]

Names of the sections that had nothing to collect.

No settings file was named, the project has no database, the run wrote no warnings. Each is a fact about the invocation rather than a failure, and each is still listed, because the report recipient must be able to tell “there were no warnings” from “the warnings were not gathered”.

Returns:

section names, in collection order.

property problems: List[str][source]

Names of the sections that failed while being gathered.

Distinct from omitted: a section that had nothing to collect is not a failure, and the two are listed separately.

Returns:

section names, in collection order. Empty on a clean report.

spacr.crashreport.ai_summary_prompt(summary: Mapping[str, Any]) → str[source]

The fixed prompt that asks the user’s own AI to summarise a report.

The AI sees only the anonymised summary, never the traceback, log or settings, and what it writes is scrubbed again before it is sent.

Parameters:

summary – a summary from public_summary().

Returns:

the prompt.

spacr.crashreport.anonymous_report_notice_due() → bool[source]

Whether the one-time notice about anonymised reports is still to show.

Returns:

True until mark_anonymous_report_notice_shown().

spacr.crashreport.anonymous_reports_enabled() → bool[source]

Whether any failure is sent as an anonymised report.

Returns:

False for the choices that send nothing.

spacr.crashreport.build_parser() → argparse.ArgumentParser[source]

Return the spacr-crashreport argument parser.

Returns:

the parser, built separately so a test can exercise the argument surface without running a collection.

spacr.crashreport.classify_failure(exception: BaseException | None = None, traceback_text: str = '') → str[source]

Return whether a failure is a fault or a user error.

A spacr.errors.ConfigurationError, or any of its subclasses, is a user error: a setting, a validation or a missing input the user can fix. Anything else is a fault.

Parameters:
  • exception – the exception, when at hand.

  • traceback_text – a formatted traceback otherwise.

Returns:

KIND_USER_ERROR or KIND_FAULT.

spacr.crashreport.collect(run_id: str | None = None, *, db: os.PathLike | None = None, settings: os.PathLike | None = None, settings_values: Mapping[str, Any] | None = None, checkout: os.PathLike | None = None, app: str = '', exception: BaseException | None = None, note: str = '', probe_gpu: bool = False) → CrashReport[source]

Collect the crash-report sections into a CrashReport.

Composed from spacr.doctor, spacr.runctx, spacr.errors and spacr.logging_util rather than collected again, so the report cannot disagree with what those tools say when run directly.

Parameters:
  • run_id – the run to include. Defaults to find_last_run_id(), which prefers an active run_context over the newest log on disk.

  • db – project database, e.g. plate1/measurements/measurements.db. Adds the doctor’s database checks and the run-status stamps.

  • settings – settings csv/json that was run.

  • settings_values – the settings as a mapping, when the caller has them in memory. Takes precedence over settings.

  • checkout – directory the user believes they are editing; defaults to the current one; the report states whether the running spaCR is imported from it.

  • app – app key for the settings file (mask, measure, …).

  • exception – the exception being reported, if any. Its traceback goes in verbatim.

  • note – free text from the user — what they were doing.

  • probe_gpu – let the doctor allocate on the GPU to prove it works. Off by default: a probe that fails would add a failure that is not the one being reported.

Returns:

the assembled CrashReport. Never raises; a section that could not be gathered is named in CrashReport.problems.

spacr.crashreport.diagnose_remotely(summary: Mapping[str, Any], question: str = '', *, timeout: float = DIAGNOSE_TIMEOUT_S) → Dict[str, str][source]

Ask the relay’s hosted model about an error, with no account.

Sends the anonymised summary and the scrubbed question, cut to MAX_QUESTION_CHARS, to /diagnose. The answer is generated advice, not a checked fix. Blocks, so call it off the GUI thread.

Parameters:
  • summary – a summary from public_summary().

  • question – what the user asks; may be empty.

  • timeout – seconds the model may take.

Returns:

{"advice": text, "model": model name}.

Raises:

RelayRefused – with reason "quota" when the free daily limit is used up, "network" when the relay cannot be reached or the network is forbidden, and "error" otherwise.

spacr.crashreport.error_report_mode() → str[source]

Return the “AI Error report” choice, a key of ERROR_REPORT_MODES.

A profile that only ever turned the older on/off switch off reads as "off".

Returns:

the saved key, or DEFAULT_ERROR_REPORT_MODE.

spacr.crashreport.example_public_summary() → Dict[str, Any][source]

Build a summary from a made-up error, to show what a report holds.

The error’s message names a plate file under the home folder, an e-mail address and a token, so the example shows each one removed.

Returns:

a summary built exactly as a real one is.

spacr.crashreport.explanation_key(traceback_text: str, module: str = '', kind: str = '', message: str = '') → str[source]

explanation_signature() of a failure, without the environment.

Reads only the traceback, so it is cheap enough for the GUI thread and gives what the full public_summary() of the same failure gives.

Parameters:
  • traceback_text – the formatted traceback.

  • module – the spaCR module key.

  • kind – KIND_FAULT or KIND_USER_ERROR.

  • message – the message sent in place of the exception’s own.

Returns:

the signature.

spacr.crashreport.explanation_signature(summary: Mapping[str, Any]) → str[source]

The error’s signature, computed as the relay computes it.

The exception type, the module and the innermost five frames (file:function); a user error adds its normalised problem list. The relay keys its saved explanations, and its issues, by the same value.

Parameters:

summary – a summary from public_summary().

Returns:

the SHA-256 hex digest.

spacr.crashreport.fetch_allowance(timeout: float = SEND_TIMEOUT_S) → Dict[str, Any] | None[source]

Ask the relay how much of today’s free allowance this install has left.

A GET of /allowance?install_id=...; it uses up nothing. Blocks, so call it off the GUI thread.

Parameters:

timeout – seconds the request may take.

Returns:

{"ai": {"best_left", "light_left"}, "reports_left", "pool", "resets_at"} with pool one of best, light or exhausted and resets_at an ISO-8601 UTC time; None when the network is forbidden or the relay gives no answer.

spacr.crashreport.find_last_run_id(problems: List[str] | None = None) → str[source]

Return the id of the most recent run this machine logged.

The run ids live one JSONL file per run under spacr.runctx.runs_log_dir(), so “the last run” is the newest file there. An active run wins over it: inside a run_context the crash being reported is this run, not the one before it.

Parameters:

problems – optional list that anything which went wrong on the way to the answer is appended to. It distinguishes “no run was found” from “the run directory could not be read”. collect() passes one and puts it in the manifest.

Returns:

the run id, or '' when none could be identified. Never raises – a missing log directory is an ordinary answer here.

spacr.crashreport.flush_report_queue(timeout: float = SEND_TIMEOUT_S) → Dict[str, int][source]

Try to send every waiting report, oldest first.

Stops at the first report that has to wait again (no network, a server error or the relay’s quota), so an offline computer makes one attempt, not twenty. Does nothing while another send is running.

Parameters:

timeout – seconds each post may take.

Returns:

{"sent": n, "dropped": n, "kept": n}.

spacr.crashreport.install_excepthook(destination: os.PathLike | None = None, **kwargs: Any) → Callable[source]

Write a crash report on any unhandled exception, then behave as before.

Chains rather than replaces: the previous sys.excepthook still runs, so the traceback the user is used to seeing still appears, with one line after it saying where the bundle is. With anonymised reports on (anonymous_reports_enabled()), the crash’s public_summary() is also queued and sent on a background thread; the bundle is never sent.

Parameters:
Returns:

the hook that was installed, so a caller can restore sys.excepthook to what it was.

spacr.crashreport.install_id() → str[source]

Return this profile’s random install id, creating it on first use.

The id is a random UUID, not derived from the computer, the user or the network. The relay uses it only to share its free allowance fairly.

Returns:

the id as a string.

spacr.crashreport.main(argv: Sequence[str] | None = None) → int[source]

Command-line entry point. python -m spacr.crashreport.

Parameters:

argv – arguments, defaulting to sys.argv[1:].

Returns:

0 when the bundle was written, 1 when it could not be — and never anything else; a section that could not be gathered does not change the exit code.

spacr.crashreport.mark_anonymous_report_notice_shown() → None[source]

Record that the notice about anonymised reports has been shown.

spacr.crashreport.mode_code_context(mode: str | None = None) → bool[source]

Whether a choice adds each spaCR frame’s source line to a report.

Parameters:

mode – a key of ERROR_REPORT_MODES; defaults to the saved one.

Returns:

True only for "all_code".

spacr.crashreport.mode_explains(mode: str | None = None) → bool[source]

Whether a choice has the AI explain every failure in the console.

Parameters:

mode – a key of ERROR_REPORT_MODES; defaults to the saved one.

Returns:

True for the four “Summarize” choices.

spacr.crashreport.mode_sends(kind: str, mode: str | None = None) → bool[source]

Whether a choice files a failure of kind as an issue.

Parameters:
  • kind – KIND_FAULT or KIND_USER_ERROR.

  • mode – a key of ERROR_REPORT_MODES; defaults to the saved one.

Returns:

whether it is sent.

spacr.crashreport.network_allowed() → bool[source]

Whether a report or a hosted diagnosis may use the network at all.

Returns:

False when NO_NETWORK_ENV is set or spaCR runs in safe mode, True otherwise.

spacr.crashreport.next_reset_utc(now: datetime.datetime | None = None) → datetime.datetime[source]

Return the next 00:00 UTC, when the relay’s daily allowances reset.

Parameters:

now – the moment to count from; defaults to now.

Returns:

an aware UTC datetime.

spacr.crashreport.normalise_cause(raw: Any) → str[source]

A likely-cause key as the relay keeps it: kebab-case, 5 words, 40 chars.

Parameters:

raw – the key as the AI wrote it.

Returns:

the slug, or "".

spacr.crashreport.normalise_question(text: Any) → str[source]

A question as both caches compare it.

Lower case, placeholders such as <PATH> removed, and every run of whitespace or punctuation folded into one space, the way the relay’s normaliseQuestion does it.

Parameters:

text – the question.

Returns:

the normalised question.

spacr.crashreport.public_environment() → Dict[str, Any][source]

The computer and libraries a report was made on, without any name.

OS version, CPU architecture and cores, memory rounded to whole GB, the GPU’s model and memory when torch already knows it, CUDA or MPS, the versions of spaCR’s main libraries and how spaCR was installed.

Returns:

the facts that could be read.

spacr.crashreport.public_input_facts(settings: Mapping[str, Any] | None, limit: int = 5000) → Dict[str, Any][source]

What the run’s source folder holds, without any name in it.

Counts the image files directly in the folder (at most limit entries are looked at), the extensions seen, and the shape, dtype and likely channel count of the first image, read from its header.

Parameters:
  • settings – the run’s settings.

  • limit – the most folder entries looked at.

Returns:

{"images", "extensions", "shape", "dtype", "channels"}, only the keys that could be found; {} without a readable folder.

spacr.crashreport.public_log_tail(text: str, lines: int = MAX_LOG_TAIL_LINES, limit: int = MAX_LOG_TAIL_CHARS) → str[source]

The console’s last lines before a failure, scrubbed and cut.

Parameters:
  • text – the console text.

  • lines – the most lines kept, from the end.

  • limit – the most characters kept, from the end.

Returns:

the scrubbed excerpt.

spacr.crashreport.public_problem_lines(problems: Sequence[Any], limit: int = MAX_USER_ERROR_MESSAGE_CHARS) → List[str][source]

Scrub settings problems into short lines that may be published.

Paths and file names are taken out rather than replaced, so “src does not exist: /data/plate1” is sent as “src does not exist”.

Parameters:
Returns:

one line per problem, "setting: message" when the problem names a setting; duplicates and empty lines are dropped.

spacr.crashreport.public_settings(settings: Mapping[str, Any] | None) → Dict[str, Any][source]

The settings a run used, with nothing personal left in them.

Setting names are kept as they are; values go through _public_setting_value(), so src reads <path: exists=no> when the folder is missing, without the folder’s name. At most MAX_SETTINGS settings, in name order.

Parameters:

settings – the run’s settings.

Returns:

{name: value}.

spacr.crashreport.public_summary(exception: BaseException | None = None, *, traceback_text: str = '', module: str = '', user_note: str = '', dry_run: bool = False, kind: str = '', code_context: bool = False, screen: str = '', error_text: str = '', message: str = '', settings: Mapping[str, Any] | None = None, log_text: str = '', run: Mapping[str, Any] | None = None, diagnostics: bool = False) → Dict[str, Any][source]

Build the anonymised summary of an error that may be published.

Only the keys in PUBLIC_SUMMARY_KEYS are present. The message is passed through scrub_public_text() and cut to MAX_MESSAGE_CHARS; the frames are at most MAX_FRAMES {file, function, line} records with the file as a basename, or <user-code> for a file outside spaCR, Python and its libraries. No log, setting, environment variable or path is read into it.

Parameters:
  • exception – the exception, with its traceback.

  • traceback_text – a formatted traceback, used when no exception object is at hand (a failed pipeline run reports one as text).

  • module – the spaCR module key the error happened in, such as mask or measure.

  • user_note – what the user adds, scrubbed and cut to MAX_NOTE_CHARS; left out when empty.

  • dry_run – ask the relay to validate without filing; left out when false.

  • kind – KIND_FAULT or KIND_USER_ERROR; worked out by classify_failure() when empty.

  • code_context – add each spaCR frame’s source line as code. Frames of any other file never carry code.

  • screen – the screen’s title as the user sees it, such as Mask; at most MAX_SCREEN_CHARS characters.

  • error_text – the error as the console showed it, scrubbed with its line breaks kept and cut to MAX_ERROR_TEXT_CHARS.

  • message – the message to send in place of the exception’s own, such as every problem a refused run found; a user error keeps its line breaks and may be MAX_USER_ERROR_MESSAGE_CHARS long.

  • settings – the run’s settings; their names and public values (public_settings()) and the source folder’s input facts (public_input_facts()) are added.

  • log_text – the console text before the failure; its last lines are added, scrubbed (public_log_tail()).

  • run – {"stage", "elapsed_s", "dry_run"} of the failed run.

  • diagnostics – add the environment block (public_environment()) and the exception chain.

Returns:

the summary, ready for submit_public_summary().

spacr.crashreport.question_key(question: str, place: str = '', context_chars: int = 0) → str[source]

The key a question’s saved answer is kept under, or "".

SHA-256 of the scrubbed, normalised question, the place it was asked (screen or module) and spaCR’s major.minor, prefixed q:. A question with more than QUESTION_CACHE_CONTEXT_MAX characters of question and pasted context is never saved.

Parameters:
  • question – what the user asked.

  • place – the screen or module it was asked on.

  • context_chars – characters of console context, code or traceback pasted with it.

Returns:

the key, or "" when the answer must not be saved.

spacr.crashreport.queued_summaries() → List[Dict[str, Any]][source]

Return the reports waiting to be sent, oldest first.

Expired and surplus reports are deleted first.

Returns:

the summaries.

spacr.crashreport.relay_url() → str[source]

Return the relay address reports are posted to, without a trailing slash.

REPORT_RELAY_ENV overrides REPORT_RELAY_URL when it holds an https:// address, or an http:// one on this computer.

Returns:

the base address.

spacr.crashreport.report_exception(exception: BaseException, destination: os.PathLike | None = None, **kwargs: Any) → str[source]

Write a crash report about exception and return where it went.

The call to make from an except block that is about to re-raise.

Parameters:
Returns:

the path written, or '' when even writing the file failed – which is reported on stderr rather than raised, because this is called from a failure path and must never replace the user’s exception with one of its own.

spacr.crashreport.save_explanation(signature: str, text: str, tier: str = '', model: str = '', now: float | None = None, cause: str = '') → None[source]

Keep an explanation on this computer for the next identical error.

At most MAX_SAVED_EXPLANATIONS are kept, oldest dropped first, and expired ones are dropped on every write. A failed write is ignored.

Parameters:
  • signature – from explanation_signature().

  • text – the scrubbed explanation.

  • tier – "best", "light" or "".

  • model – the model that wrote it, when known.

  • now – the time, in seconds since the epoch; defaults to now.

  • cause – the likely-cause key the AI gave, if any.

spacr.crashreport.saved_explanation(signature: str, now: float | None = None) → Dict[str, Any] | None[source]

The explanation saved on this computer for an error, if still good.

Good means written for this spaCR major.minor and less than SAVED_EXPLANATION_DAYS days ago.

Parameters:
Returns:

{"text", "tier", "model"}, or None.

spacr.crashreport.scrub_public_lines(text: Any, limit: int = MAX_AI_SUMMARY_CHARS, keep_docs_urls: bool = False) → str[source]

Scrub a text like scrub_public_text(), keeping its line breaks.

Each line is scrubbed on its own, so a Markdown answer keeps its headings and numbered steps on lines of their own; runs of blank lines become one.

Parameters:
  • text – the text; anything else is turned into text first.

  • limit – the longest result, in characters; a longer one is cut and ends in ….

  • keep_docs_urls – as for scrub_public_text().

Returns:

the scrubbed text, lines joined by \n.

spacr.crashreport.scrub_public_text(text: Any, limit: int = MAX_MESSAGE_CHARS, keep_docs_urls: bool = False) → str[source]

Remove anything personal from a short text that will be published.

Absolute paths (POSIX, Windows and network shares) become <PATH>, file names with a data or code extension <FILE>, e-mail addresses <EMAIL>, web addresses <URL>, IP addresses <IP>, host names <HOST>, this computer’s login name <USER>, and anything shaped like a key, token or long hexadecimal or base64 string <TOKEN>. The redaction helpers of spacr.qt.ai.issue_report run first, and these stricter rules after them.

Parameters:
  • text – the text; anything else is turned into text first.

  • limit – the longest result, in characters; a longer one is cut and ends in ….

  • keep_docs_urls – keep the public spaCR addresses in PUBLIC_DOCS_URLS, for an AI answer that links the documentation; every other address still becomes <URL>.

Returns:

the scrubbed text on one line.

spacr.crashreport.set_anonymous_reports_enabled(enabled: bool) → None[source]

Turn reports on or off, as the first-run notice’s switch does.

On restores the default choice when the current one sends nothing; off is "off".

Parameters:

enabled – whether reports may be sent.

spacr.crashreport.set_error_report_mode(mode: str) → None[source]

Save the “AI Error report” choice.

A choice that sends nothing also deletes every report still waiting, so nothing queued before the change is sent after it.

Parameters:

mode – a key of ERROR_REPORT_MODES.

Raises:

ValueError – for any other value.

spacr.crashreport.split_cause(text: str) → Tuple[str, str][source]

Take the CAUSE: <slug> line off an AI answer.

Parameters:

text – the answer.

Returns:

(answer without the line, slug or "").

spacr.crashreport.submit_public_summary(summary: Mapping[str, Any], *, timeout: float = SEND_TIMEOUT_S) → Dict[str, Any][source]

Send an anonymised summary to the relay, or queue it for later.

Blocks for up to timeout seconds, so call it off the GUI thread, or use submit_public_summary_in_background(). With reports off, or the network forbidden, nothing is sent and nothing is stored.

Parameters:
  • summary – a summary from public_summary().

  • timeout – seconds the post may take.

Returns:

{"status": ...} with REPORT_SENT (and issue_number, issue_url, deduplicated), REPORT_QUEUED (kept to try again), REPORT_DROPPED (rejected, not retried) or REPORT_DISABLED.

spacr.crashreport.submit_public_summary_in_background(summary: Mapping[str, Any], on_done: Callable[[Dict[str, Any]], None] | None = None) → threading.Thread | None[source]

Queue a summary now and send it on a daemon thread.

The summary is on disk before this returns, so a process that ends at once still sends it the next time spaCR starts.

Parameters:
Returns:

the thread, or None when reports are off and nothing was queued.

spacr.crashreport.write_crash_report(destination: os.PathLike | None = None, **kwargs: Any) → str[source]

Gather a report and write it as one zip.

Parameters:
  • destination – where to write. A directory gets spacr-crashreport-<run id or timestamp>.zip inside it; anything else is used as the file name. Defaults to <log dir>/spacr-crashreport-<...>.zip, the spaCR log directory.

  • kwargs – passed straight to collect().

Returns:

the absolute path of the file written.

Raises:

OSError – only when the destination itself cannot be written, This failure is raised rather than recorded, so the caller is never given the path of a report that is not on disk.

Example

from spacr.crashreport import write_crash_report
path = write_crash_report(db='plate1/measurements/measurements.db')
print(f'attach {path} to the issue')

Nested helpers

_doctor_sections.gather_json() → str

Return retained Doctor rows as JSON, or raise if collection failed.

spacr/crashreport.py:509

_doctor_sections.gather_text() → str

Run Doctor, retain its rows and summary, and return formatted text.

spacr/crashreport.py:491

_main_log_section.gather() → str | None

Return the capped main-log tail and record its path and size facts.

spacr/crashreport.py:624

_run_log_section.gather() → str | None

Return the capped run-log tail and record its path and size facts.

spacr/crashreport.py:524

_run_status_section.gather() → str | None

Return project run-status rows as JSON when a database provides them.

spacr/crashreport.py:604

_run_summary_section.gather() → str | None

Return WARNING-and-higher run records as readable text, if any.

spacr/crashreport.py:548

_settings_section.gather() → str | None

Return explicit or recorded settings as JSON, or None if absent.

spacr/crashreport.py:574

collect.environment() → str

Return redacted environment JSON and record every redacted name.

spacr/crashreport.py:726

install_excepthook.hook(kind, value, tb) → None

Report, then delegate to the hook that was installed before.

spacr/crashreport.py:844

scrub_public_text._hold(match: 're.Match') → str

Keep a public spaCR address aside; any other becomes <URL>.

spacr/crashreport.py:1416

submit_public_summary_in_background._run() → None

Send the queued summary and hand the outcome to on_done.

spacr/crashreport.py:2568