54: Power User Stability - transcript

Download the MP3

Jessica: The QUILL Cast, episode fifty-four, the finale. I'm Jessica.

Liam: And I'm Liam. Fifty-four episodes, one last topic, and the most honest topic of the whole course: what happens when things go wrong, and what QUILL has done in advance to make sure going wrong doesn't take you with it.

Jessica: Last week was the power user for accessibility. We walked the verbosity knobs, the F1 context help, the way JAWS and NVDA read the editor, and the dialog inventory gate that audits every modal we ship. We also surfaced the fact that the prior ep-fifty-two script was titled "the finale," and we owe you a correction: fifty-two was a strong checkpoint, but the course was always meant to close on stability. So here we are, the real finale.

Liam: Today's frame is the layer of the codebase that never gets a screenshot, the stability layer, and the two-way conversation it has with you. Stability in QUILL is not a promise. It is a set of small contracts, each with a test, a gate, and a code path you can read. The four pieces we'll cover: the redaction rules that scrub secrets before they ever leave your machine, the diagnostic bundle that turns a crash into a redacted zip you can hand to support, the no-silent-network audit that fails the build if a new outbound call sneaks in, and Safe Mode, the single switch that turns the noisy half of the product off.


Jessica: A quick framing note before we touch anything. We are not asking you to be paranoid. We are asking you to know the shape of the safety net, because a safety net you cannot describe is one you cannot trust. By the end of this episode you should be able to say, out loud, what gets stripped from a crash report, what counts as a network call in the audit, and what changes when you flip on Safe Mode. If you can say those three things, the product has earned your trust on its merits.

Liam: And one honest correction up front, because we promised this course would do that. The brief lists a `quill/ui/help_menu.py` file as a code reference for this episode. That file does not exist. The Help menu and its commands live in `quill/ui/main_frame_menu.py` and `quill/ui/main_frame.py`, with the help commands registered through the command registry under ids like `help.save_diagnostics` and `help.report_bug`. We'll cite the right files as we go. We mention this not to nitpick, but because the absence of a help menu module is itself a small lesson: in a project this size, the help system is a cross-cutting concern, a mixin attached to the main frame and to any dialog that opts in, not a single file you can point at. The F1 help renderer is loaded once at app start from a topics file, and every dialog that wants F1 help pulls it in through that mixin. Knowing that, when you read the source, makes the rest of the architecture easier to follow.


Jessica: Do this now, before we touch the rest. Open QUILL if you have it handy. Press the Help menu, then choose About QUILL, just to confirm the menu path on your machine. Close the dialog. That one press is the entire shape of what this episode is about: a visible affordance, a known location, a known effect. If you cannot find the Help menu, you have just found a real bug, and the rest of the episode will tell you exactly how to report it. Pause the audio, do the step, then come back.

Liam: Welcome back. Let's start with the part of stability most people never see, the redaction rules in `quill/stability/redaction.py`. This module is the single point through which the crash bundle builder and the safe-subprocess logger pass any string that might contain a secret, a file path, or an email. It is intentionally dependency-free and platform-neutral so it can be imported from `quill.core`, which is wx-free, and from the early-startup logging configuration.

Jessica: Here is what the rules actually match, and we are going to be specific, because vague promises are not what you came for. The redaction engine knows six shapes. First, name equals value patterns for any of api key, token, secret, password, passphrase, authorization, access key, client secret, cookie, session, signature, hmac, ssh key, private key, bearer. The match is case-insensitive, and the value is replaced wholesale with the marker REDACTED. Second, long hex and base64-looking tokens, thirty-two characters or more, replaced with the marker TOKEN. Third, modern API key prefixes, GitHub PATs starting with ghp underscore or github pat underscore, OpenAI keys starting with sk hyphen, AWS access keys starting with AKIA, Slack tokens starting with xoxb or xoxp, replaced with TOKEN. Fourth, JSON web tokens, the three-segment eyJ dot pattern, replaced with JWT. Fifth, common email addresses, replaced with EMAIL. Sixth, absolute paths under your Windows user profile or your POSIX home, replaced with the marker PATH, and the redaction is careful: it keeps the trailing file name so the log line is still readable, but the user-specific prefix is gone.

Liam: A subtle point worth saying out loud. The redaction rules are intentionally conservative. The docstring of the helper that checks whether a whole line is a pure secret says it directly: false positives are fine, the user can run a redaction receipt to see what was dropped, false negatives are not, because we promised to drop secrets. If a line begins with "Bearer ", "Basic ", "Authorization:", or "X-API-Key:", the whole line is dropped, not just the value, because there is no useful version of a line that starts that way.

Jessica: And one more subtlety: the redaction is byte-bounded. Every line included in a diagnostic bundle is capped at four thousand and ninety-six bytes, with a tail marker that reads truncated. Long log lines, very long stack traces with embedded payloads, faulthandler output with megabytes of locals: all of those are still useful after truncation, but the bundle never carries a single line that could itself be a small file.

Jessica: There is also a second, lighter-weight redaction helper called `redact_source_tokens`, and it is worth knowing the difference. Where `redact_text_for_bundle` strips paths, emails, and any line that looks like a pure secret, `redact_source_tokens` only replaces long tokens and JWTs, and it leaves paths and emails in place. It exists because the console history and the in-app log reader are read by you, the user, in your own session. You do not want your own file paths redacted out of your own console; you want accidental key pastes masked, and that is the job `redact_source_tokens` does. The two helpers are a deliberate split: the bundle is for sharing, the console is for you, and the rules match the audience.


Liam: Now the part you actually touch: the diagnostic bundle. The function lives in `quill/stability/crash_report.py`, and it is called `build_diagnostic_bundle`. The Help menu command that reaches it is `help.save_diagnostics`, labeled Save Diagnostics. When you pick it, the file save dialog appears with the title Save Diagnostics, and the suggested filename is a zip with a UTC timestamp.

Jessica: The output path is computed for you. The zip is written into a diagnostics folder under your QUILL app data directory, with a name in the form `quill-diagnostic-bundle-YYYYMMDDTHHMMSSZ.zip`. If the function is called from somewhere that wants to control the path, it accepts an override, but the user-facing flow always lands the file in the standard place so support knows where to look.

Liam: Here is the part that matters for trust. The bundle is built in two passes. The first pass reads every text file the bundle will include, runs each line through the redaction rules, and accumulates a count of dropped and truncated lines per file. The second pass writes metadata.json with those counters, then writes the redacted text bodies. The four files it considers, and only these four, are quill.log, the regular application log, faulthandler.log, the low-level crash dump, thread-dump.log, the snapshot of every thread, and memory-snapshot.txt, the heap summary. If any of those files does not exist on disk, it is silently skipped, the bundle is still built, and the redaction counters reflect what was actually redacted.

Jessica: After the zip is written, the redaction is logged in the application log at info level with a single line that reads: "Diagnostic bundle redaction: N lines dropped, N lines truncated across N files." That line is the receipt. If you ever wonder whether the bundle you are about to send has been through the redaction pass, look for that line in the same log the bundle was built from. If the line is absent, the bundle was empty, which is also fine, and the absence of the line is itself the signal.

Liam: Two more pieces of the bundle you should know about. The recent commands list, the last several command ids you ran, is filtered through a command-id grammar before it lands in the bundle. The grammar is a regex: a lowercase letter, then up to sixty-three characters of lowercase letters, digits, dot, dash, or underscore. Anything that does not match is dropped. That keeps the bundle from carrying arbitrary strings that might happen to look like commands but are actually content. And the active tasks list, if one is provided, is serialized from its dataclass form so the bundle tells support what background work was in flight at the moment of the crash, without leaking the futures themselves.


Jessica: The next layer is the one that protects you from QUILL itself, the no-silent-network audit in `quill/tools/network_egress_audit.py`. The docstring calls it a gate, gate nine, and the structure is small enough to describe in one breath. The audit walks every Python file under the quill package, parses it with the standard library AST, and looks for calls to a small set of function names: urlopen, urlretrieve, and ElevenLabs, which is in the list because the ElevenLabs SDK does its HTTP internally, so the construction of the SDK client is the only reviewable marker in source.

Liam: For every call it finds, the audit records a site identifier in the form "relative path double colon enclosing function." That site identifier is then compared against a hand-maintained map called `_REVIEWED_EGRESS`, and every entry in that map has to be a real site with a written justification. The justification has to answer two questions, and the docstring says it directly: what triggers the call, and why is it not a silent call. A trigger is either a user action, a visible progress surface, or an opt-in setting. A reviewer adding a new network call has to add an entry, which forces a code-review touchpoint, and the build breaks if the new call site is not in the map.

Jessica: To make this concrete, here are three examples from the current map, picked because they cover the three trigger categories. The Auphonic post-production client, in `core/publish/auphonic.py`, function `_request`, is reached only from the publish dialog's explicit buttons and the AI Hub Services tab's Check Account and Credits button. The trigger is a user action, the consent surface is the publish dialog that names the service, and the entry notes that the API token lives in the Windows Credential Manager, never in settings.json. The metadata lookup in `core/metadata_lookup.py`, function `_http_json`, is reached only by the Audio Studio's "Look up book details" button, which names Open Library and MusicBrainz before the first call. The trigger is a button press, the consent surface is the button label, and the call is HTTPS-only over a verified TLS context. The GLOW engine update check in `core/glow_updates.py`, function `fetch_glow_manifest`, runs only when the user invokes "Check for GLOW Updates" or enables the GLOW auto-check setting. The trigger is an opt-in setting, and the manifest is fetched over a host-allow-listed HTTPS URL with a verified TLS context.

Liam: And the audit is honest about its own blind spots. PyGithub, the GitHub Python client, makes HTTPS calls internally through urllib3, so its call sites never appear in the quill source as urlopen. The audit documents those entry points by hand, in a comment block at the bottom of the file, so a reviewer can still see the full network surface even though the AST cannot see it. The pip subprocess egress, used by the three optional speech engine installs and the optional agent SDK packs, is documented the same way: pip reaches PyPI on its own, not through a urlopen in our source, so the AST scanner cannot see those calls, but they are written down in the same comment block, with the same trigger rules: explicit user action, visible progress dialog, blocked in Safe Mode, wheel-only install, no admin elevation.

Jessica: The takeaway is structural, not procedural. There is no policy document that says "try not to phone home." There is a code-level gate that fails the build if a network call appears without a review entry. A new contributor cannot accidentally add a silent network call. The shape of the safety is the shape of the build.

Liam: And the audit does one more thing that is easy to miss. It exposes a discover_egress_sites function, so a developer can run the inventory by hand, read the map, and see exactly what the build is about to be checked against. The map is not a private ledger. It is checked in, in the same file as the scanner, and it carries the rationale for every entry inline. A reviewer reading the diff for a new feature can see the egress entries the feature depends on, can read the justification, and can push back on the justification if it is weak. That is the review touchpoint the gate is designed to force, and it works because the rationale is right there, not in a wiki.


Liam: The last layer is the simplest to describe and the most powerful to use. Safe Mode is one switch. The contract lives in `quill/stability/safe_mode.py`. When the environment variable `QUILL_SAFE_MODE` is set to the string "1", or when the command line flag `--safe-mode` is passed, the function `should_enable_safe_mode` returns true, and the function `build_safe_mode_config` produces a frozen configuration object whose defaults are all true. The defaults are: disable plugins, disable experimental features, disable AI integrations, disable startup restore, disable background indexing, disable file watchers, disable custom themes, disable custom snippets, disable network services.

Jessica: In plain English: Safe Mode is the clean room. AI off. Extensions off. Automation off. Network surfaces off. Custom themes and snippets reverted to bundled defaults. File watchers stopped. Background indexing paused. The opening behavior of the app is also changed: the trust-consent prompt is suppressed on the assumption that you came here precisely because you do not want to be prompted, and the feature and macro managers load in non-persistent mode, meaning any changes you make to the feature toggles or the macro list while in Safe Mode are not written to disk.

Liam: The safest way to use Safe Mode is from a desktop shortcut. Create a shortcut to `python -m quill`, open its properties, and add `--safe-mode` to the target. Now you have a known-good launcher for the moments when a Quillin is misbehaving, when a theme is fighting your screen reader, when a watch folder is hammering the disk, or when you are about to do something with a sensitive document and you want the network surfaces physically unable to fire. The setting does not persist into the next normal launch. You have to ask for it every time, and that is the point.

Jessica: The Help menu has a related option we have not named yet, and it is worth a mention before we close the loop. The "Open Logs Folder" command, registered as `help.open_logs_folder`, opens your QUILL logs folder in the system file manager. If you want to see for yourself what gets logged, what gets redacted, and what is preserved verbatim, that command is the one. The logs folder is also where the "Clear Logs" command reaches, and where the recovery dialog's "Send Bug Report" button reads from when it builds the GitHub issue payload. The recovery dialog, the one that appears after a hard crash, has a row of buttons: Restore Latest Snapshot, Open Logs Folder, Clear Logs, Save Diagnostics, Send Bug Report, and Skip Recovery. That is the full incident response surface, in the order you would use it: restore, look, clean, save, send, move on.

Liam: There is a separate, gentler path for the cases that are not crashes at all. The "Report a Bug" command, registered as `help.report_bug`, opens the bug report dialog without any crash context. It is the right place for confusing behavior, accessibility regressions, menu items that go to the wrong place, any time something is wrong but nothing has actually broken. The crash report dialog is for the moment things break; the bug report dialog is for the moment you notice something is off. Both are reachable from the Help menu, both are reachable by keyboard, and both stay off the network until you press the button that says send.


Liam: One correction, then the homework. While writing this episode we noticed that the diagnostic bundle path, the `app_data_dir()/diagnostics/` folder, is created on demand the first time the bundle is built. If you have never built a bundle, the folder does not exist. That is not a bug, and it is worth saying so: there is no empty-folder litter, and the path is documented in the function. But if you go looking for the folder after a clean install, you will not find it until you build your first bundle. If that surprises you, you are not missing anything.

Jessica: Another correction, the one we already named: the prior episode, fifty-two, was titled "the finale" in its file. We are not rewriting history. We are clarifying that the course as planned has fifty-four episodes, and the final beat belongs to stability, the layer that makes the trust in every prior episode load-bearing. If you listened to fifty-two and felt it landed, it did. This is the second half of the same landing.


Liam: Homework. Four steps, all small, all real. Step one: open the Help menu, find Save Diagnostics, and run it. Open the resulting zip in your file manager. Inside, look for metadata.json. Read the redaction block. Notice the line counts. That is the redaction rules in your hands, working on your logs. Step two: open a terminal, set the environment variable `QUILL_SAFE_MODE` to "1" on Windows, or `export QUILL_SAFE_MODE=1` on macOS, and launch QUILL with `python -m quill`. Confirm that the AI menu items are disabled, the Quillins menu is greyed out, and the watch folder status bar entry is absent. Step three: in a normal launch, with Safe Mode off, pick File, Open, and open a file from a recent location on an unplugged drive. Watch the error: it tells you the path could not be reached, and it does not hang, and it does not write a corrupted empty file in its place. That is the safe-subprocess and the atomic-save contracts cooperating, and it is what you should expect from a stability layer that earned its name. Step four: press F1 with the cursor inside the main editor. Read the help topic. Then press F1 with the cursor inside a modal dialog. Read the help topic. The verbosity layer you heard about last week and the stability layer you heard about today are the same layer, viewed from two sides: the help tells you what a control does, the stability layer makes sure the control does it safely.

Jessica: And one step that is not homework, only an invitation. If you have ever had a crash and wondered whether your bundle was clean enough to send: it was. If you have ever worried that a new feature might phone home without asking: it cannot, the build will not let it. If you have ever wanted a clean room to test a Quillin in: it is one environment variable away. Those three sentences are the whole promise of this layer. Hold us to them.


Liam: This is the last beat of the course. Fifty-four episodes, one product, one rule: the rules are not in the marketing, they are in the build, and you can read them. The five enforced invariants: no silent network, no silent installs, atomic saves, specific consent, escape hatches guaranteed. The two-way honesty: QUILL audits your documents' accessibility, and it submits to audits of its own behavior. The community: built in the open, the issue tracker is public, the roadmap is a document, and the people who use the product are the people who steer it.

Jessica: What is QUILL? Same answer we gave in fifty-two, because it is the same answer: proof that when you design for the screen reader first, you do not get a lesser editor, you get a better one, for everyone who loves the keyboard. We add one line for this episode: a better one, with a safety net you can read.

Liam: Thanks for taking the whole course, start to finish. Fifty-four episodes is a long walk, and you walked it with us. I'm Liam.

Jessica: I'm Jessica. Write well, write your way, and we'll see you in the next release. The QUILL Cast is a production of Community Access and Blind Information Technology Solutions. Transcripts and the full user guide are published alongside the audio. Until next time.

Back to all episodes