Before It Can Correct You, It Must See the Failure

For The Fuck, output capture is not a supporting utility; it is the evidence boundary on which many corrections depend. This chapter examines how the application chooses among a shell logger, experimental instant logging, and a fresh rerun, then connects those choices to latency, process cleanup, output fidelity, and missing candidates. The practical judgment is whether a proposed repair is based on what the failed command actually printed, on a reconstructed terminal record, or on no usable output at all.

Output is evidence, not a free by-product

A command's text tells The Fuck what the user attempted. Its output often tells the application why that attempt failed. “Command not found,” “branch has no upstream,” “permission denied,” and “address already in use” are not interchangeable signals. They direct a rule toward different kinds of action. One may look for a close executable, another may parse an argument supplied by Git, a third may add privilege, and a fourth may inspect the process using a port. If output is absent, an output-dependent rule should not quietly treat that absence as a particular error.

The application therefore chooses an output reader when it constructs the command context. The preferred path is an available external shell logger. If that socket is not available, The Fuck uses its experimental instant-mode log when instant mode is enabled. Otherwise it falls back to rerunning the expanded command. The order matters: a previously recorded terminal result can avoid replaying the command, while the fallback is available on more ordinary installations but may repeat the user's action.

This choice is a form of evidence management. A fresh rerun may give a clean, machine-readable result under a controlled environment, but the second invocation may encounter a different filesystem, network, permission, or process state. A terminal log may preserve what the user actually saw, but it must locate the right command among a stream of prompts and output. Neither source is automatically superior. The right question is what kind of fidelity a rule needs and what cost the system is allowed to incur while obtaining it.

The default path reruns the command

The fallback reader starts a subprocess with the expanded command, using the shell rather than trying to interpret every shell feature inside Python. Standard output and standard error are combined, so a diagnostic printed on standard error remains available to the same matching logic as ordinary output. The process receives the current environment merged with The Fuck's configured environment. The defaults include a C locale, a C language setting, and Git tracing, choices intended to make important command output more predictable and informative for parsers.

Rerunning is attractive because it is simple and broadly applicable. The application does not need to have intercepted the original terminal stream, and it can ask the failed program to produce its diagnostic in a fresh process. It also obtains output in a form that is easy to pass into the Command object. For a short, read-only command whose failure is deterministic, this may be an acceptable exchange: a small delay buys the exact text that several rules need.

The same mechanism creates the central risk. The command is run again, not merely inspected. Even a failed command can have effects before it reports an error, and a command whose outcome depends on current state can fail differently on the second attempt. The Fuck does not wrap the rerun in a transaction or compare the first and second executions. Its design assumes that collecting evidence this way is useful often enough, while exposing a setting and alternative logging paths for users who cannot accept the default cost.

The reader should also notice how the application decides when to stop waiting. It identifies whether the command belongs to a configured slow-command list and uses a longer allowance for those commands; ordinary commands use the normal wait limit. This is not a performance benchmark or a prediction of how long every tool will take. It is a bounded patience policy. Without a bound, a correction helper could remain attached to a command that never returns. With too short a bound, it may discard the very output that would have explained the failure.

When the wait expires, The Fuck asks the process tree to stop. It attempts to kill recursive child processes and then the parent process. If permission prevents cleanup, it records a debug message rather than treating the cleanup as guaranteed. The output reader returns None for a timed-out command. That value has a semantic consequence: rules that require output are prevented from matching. A timeout therefore does not mean “the command produced no diagnostic.” It means “the correction system could not safely obtain the diagnostic within its evidence budget.”

The rerun reader also decodes the combined stream as UTF-8 with replacement for invalid bytes. That choice favors continuity over byte-perfect preservation. A rule can still see surrounding text when a program emits malformed output, but a character that was replaced may have been part of a command name or an error message. This is another small boundary that readers should keep in mind when judging a parser-based suggestion: readable output is not necessarily identical output.

Logging trades replay for reconstruction

The external shell logger takes a different approach. Instead of launching the failed command again, The Fuck asks a Unix-domain socket for a short list of recently recorded commands. It finds the entry whose command matches the script being corrected and reconstructs the terminal display from the recorded output. A terminal emulator library, pyte, processes control sequences so that the rules receive displayed lines rather than raw cursor movement and color instructions.

This path can preserve the relationship between the original command and what the user saw, which is valuable when replay would be unsafe or when a program's result changes rapidly. But it depends on another component being present, reachable, and correctly synchronized with the interactive shell. The logger must have recorded the command, retained it in its bounded history, and returned output in the expected socket format. If any of those assumptions fails, the application cannot manufacture the missing evidence merely because a logger was configured in principle.

Experimental instant mode also avoids a fresh rerun, but its source is a terminal log rather than a socket query. The Bash and Zsh integration adds a marker to the prompt and starts a PTY-based shell logger that records the terminal stream into a bounded file. When The Fuck later needs output, the reader groups the log by prompt occurrences, searches the groups for the requested script, and feeds the matching lines through pyte. In effect, it reconstructs the screen region associated with the previous command.

That reconstruction explains the mode's prerequisites. The reader needs the prompt marker to distinguish one command from the next, and it needs a terminal shape to render the captured stream. The documented implementation supports instant mode only under Python 3 with Bash or Zsh, and it requires the script program. If the prompt changes after initialization, the marker may no longer be present. If the command cannot be located in the log, the reader reports that it is missing rather than guessing which output belongs to it.

The logger paths trade execution risk for capture complexity. They reduce the need to repeat the original command, but they introduce persistence, bounded-buffer, prompt-format, terminal, and synchronization assumptions. A logged result may also be older than the current local state by the time a rule performs an additional lookup. The output can be faithful to the past terminal event while still being incomplete evidence for the present repair. This is why “instant” should be read as a latency mode, not as a guarantee of stronger reasoning.

stateDiagram-v2 [*] --> Select Select --> Logged: external shell logger available Select --> Instant: no logger and instant mode enabled Select --> Rerun: no logger and normal mode Logged --> Output: matching terminal record found Logged --> Unavailable: record missing or unreadable Instant --> Output: prompt marker locates the record Instant --> Unavailable: log cannot be read or matched Rerun --> NormalWait: ordinary command Rerun --> SlowWait: command is marked slow NormalWait --> Output: finishes within the wait limit SlowWait --> Output: finishes within the slow wait limit NormalWait --> Unavailable: timeout SlowWait --> Unavailable: timeout Output --> Rules: output-dependent rules may match Unavailable --> Skipped: rules requiring output are skipped

The three paths also differ in where they can fail. A socket logger can be unavailable even when the shell is working. Instant mode can lose the prompt marker or the relevant part of a bounded log. Rerun can time out, repeat an effect, or observe a changed state. The output reader presents these as controlled alternatives rather than pretending that all terminal evidence has the same origin. That distinction is essential when a later rule parses a line as if it were a reliable description of the failure.

What failure to capture means

The rest of The Fuck is designed to continue when evidence is imperfect. If shell-like command splitting fails, the command model falls back to a simpler representation. If output decoding encounters invalid bytes, it substitutes a replacement character. If a rerun cannot finish, output becomes unavailable. If a rule cannot load or its matcher raises an exception, the engine logs the problem and moves on. These paths are useful because one broken command, rule, or encoding should not necessarily terminate the correction helper.

They should not be confused with successful diagnosis. A fallback split may leave a rule with enough information to recognize a simple command while losing the structure of a more complicated one. A replacement character may preserve the shape of a diagnostic while changing the token a fuzzy matcher sees. Skipping output-dependent rules may avoid a false match, but it also removes candidates that could have helped. Reliability here means that failure is bounded and observable, not that the system always recovers a useful answer.

Debug mode is the project's main way to expose those boundaries. The logs can report settings, timing, rule failures, received output, and cleanup problems. That information is more valuable than a vague statement that “nothing worked,” because it distinguishes a rule mismatch from unavailable output and a slow command from a malformed shell split. The tests reinforce this model: they check timeout behavior, child-process cleanup, invalid-byte decoding, command construction, and interactive integration for Bash and Zsh. Those tests document expected contracts, but they are not an independent measurement of reliability across every terminal and command.

Output normalization also has a subtle relationship with upstream tools. A fixed locale can reduce variation in error wording, and tracing can add diagnostic detail, but neither setting makes every program deterministic. Some commands derive their messages from versions, plugins, operating-system conventions, or runtime state. A rule that searches for a particular phrase is consequently coupled to the output form available in the environment where it runs. The capture layer can make that form more consistent; it cannot control every program that produces it.

The sensible interpretation is conditional. When an external logger has a matching record, The Fuck can reason from a captured terminal event without replaying the command. When instant mode is correctly initialized, it can reconstruct a prompt-bounded screen region under its supported shells. When neither logging path is available, rerun may provide useful evidence for a short and repeatable command. When the command is slow, stateful, interactive, or potentially destructive, the cost of collecting evidence deserves more scrutiny. If no path yields output, the absence should narrow the candidate set rather than invite a confident guess.

That is the engineering lesson of output capture. The Fuck's corrections are only as grounded as the evidence pipeline allows, and the pipeline itself is a choice among freshness, fidelity, latency, and side-effect exposure. Once the application has a command and a bounded account of what happened, the next problem is interpretation: how rules discover the failure pattern, compete to propose alternatives, and show the user what each candidate would do.

References