Where Rewriting Stops: Shell Syntax, Pipelines, and Permissions

RTK can only compress a command safely when it understands what that command actually asks the shell and the underlying tool to do. This chapter focuses on the boundary cases: compound commands, wrappers, pipelines, redirects, substitutions, exclusions, and host permissions. The practical goal is to predict when RTK will rewrite a command, when it will rewrite only part of one, and when leaving the original untouched is the more faithful optimization.

A command is a structure, not a prefix

A simple rule such as “if the line begins with git, add rtk” works only for the easiest case. Shell input is a small language. Quoting can keep an operator inside an argument. An environment assignment can appear before the executable. A wrapper can change how a tool is launched without changing which tool should receive the work. A redirect can send output to a file instead of to the agent. If RTK treats all of these as plain text, it can either miss useful rewrites or change a command's meaning.

The registry therefore begins with normalization. It strips leading environment prefixes for the purpose of identifying the executable, then restores those prefixes when it constructs a rewritten command. It can normalize an absolute binary path to the command name used by the matching rules and remove global options that occur before a Git subcommand. It also recognizes transparent wrappers: a command such as a package or environment runner may surround an inner tool while leaving the inner tool's output semantics intact. RTK can peel the wrapper, classify the inner command, and put the wrapper back around the RTK form when the combination is supported.

This is a distinction between matching and execution. Normalization creates a form the registry can reason about; it must not discard the information needed to run the user's actual command. An environment assignment still has to reach the child process. A wrapper still has to launch the same environment. A path spelling may change for classification, but the resulting command must preserve the user's intended tool and arguments. The registry's job is to find the smallest safe insertion point for rtk, not to rewrite the command into a generic approximation.

After normalization, RTK uses a typed lexer rather than repeatedly scanning for characters such as ; or |. The lexer records arguments, operators, redirects, shell constructs, quoting, escapes, and positions. That lets the registry distinguish an actual shell operator from text inside a quoted argument and keep an option's value attached to the option that consumes it. The distinction matters for both rewriting and permission checking: if the two paths disagree about where a segment starts or which token is an argument, a command can be filtered correctly but authorized incorrectly, or vice versa.

A chain joined by &&, ||, or ; can be decomposed into segments. RTK considers each segment separately and rewrites the supported ones while leaving a segment that has no safe equivalent in its original form. For example, in a command shaped like cargo fmt --all && cargo test, the first operation may remain a normal RTK-routed command and the second may be routed to the test filter. The shell still owns the sequencing: && decides whether the second segment runs after the first succeeds. RTK does not flatten the chain into one synthetic operation.

The same segment-level discipline applies to permission decisions. An explicit allow for one segment does not automatically authorize the rest of a compound command. Every non-empty segment must independently satisfy the allow rule before the overall request can be auto-approved. An ask rule on any segment keeps the request in an ask state, and a deny rule takes precedence. This prevents the optimization attached to a recognizable first segment from becoming a path around the host's treatment of a second, unrelated operation.

stateDiagram-v2 [*] --> RECEIVED RECEIVED --> INSPECT: normalize env, paths, and wrappers INSPECT --> SEGMENT: shell chain can be decomposed INSPECT --> DEFER: heredoc, substitution, or opaque grouping SEGMENT --> CLASSIFY: inspect each segment CLASSIFY --> SUPPORTED: recognized and compatible CLASSIFY --> PASSTHROUGH: unsupported, excluded, or incompatible SUPPORTED --> ALLOW: explicit host allow SUPPORTED --> ASK: host ask or default ask SUPPORTED --> DENY: host deny wins ALLOW --> REWRITE: build RTK command ASK --> REWRITE: propose rewrite, keep prompt DENY --> RAW DEFER --> RAW PASSTHROUGH --> RAW REWRITE --> FILTERED RAW --> [*] FILTERED --> [*]

The diagram's DEFER and PASSTHROUGH states are not identical, even though both lead to raw execution. Defer means the command shape is too difficult or risky to decompose with the available safety checks. Passthrough means RTK has no matching filter, the user excluded the command, or a flag combination is incompatible with the optimized route. Both outcomes protect the original command's meaning; they simply arise from different reasons.

Pipelines and ambiguous constructs

Pipelines expose why output compression is not a harmless decoration. In producer | consumer, the producer's output is the consumer's input. A filter that changes line structure, separators, ordering, or the number of records may alter what the next command receives. The fact that a command is individually supported does not prove that it is safe in the middle of a pipeline.

RTK is consequently conservative about pipeline positions. Intermediate stages remain raw. A final stage can be rewritten only when its rule is known to be argument-safe for pipeline use. The current design gives special treatment to search commands such as ordinary grep or rg when their arguments allow the rewritten form to consume the same stream safely. A producer can also be rewritten when its rule explicitly declares producer safety. These declarations are more specific than a general “this command has a filter” label.

The difference is easiest to see with a display-only consumer. A final grep usually examines text and prints a selected subset; RTK can sometimes preserve that role while compacting the search result. A command that expects a precise machine-readable stream, changes the stream's semantics, or follows the stream continuously is not equivalent. A following tail may be safe when it merely selects the end of a finite display, but a follow mode changes the lifecycle of the pipeline. RTK's rules therefore inspect relevant flags instead of treating every spelling of a familiar command as safe.

Stderr pipelines are stricter still. A combined |& pipeline joins standard output and standard error, so a filter that normally separates those streams can no longer assume which lines belong to which channel. The registry keeps the complete stderr pipeline raw rather than applying a partial rewrite based on an incomplete view. This is a small example of a larger rule: when a transformation would change the relationship between a producer and its consumer, preserving the raw pipeline is preferable to claiming a reduction that has not been established.

Some shell constructs are deliberately outside the rewrite model. A heredoc embeds a block of input whose boundaries and contents matter to the command. Command substitution runs one command to produce text used by another command. Nested shell expressions have their own parsing behavior. Parenthesized or braced groupings can hide control flow and several commands behind a boundary. A rewrite that sees only the outside spelling could alter an inner operation without checking it, or could insert rtk where the shell expects a different syntactic object.

RTK also treats file-target redirects carefully. cat, head, and tail normally suggest reading text, but a redirect can turn the same spelling into a write or otherwise change the data flow. The registry avoids classifying such forms as ordinary file reads. It has similar caution around incompatible flags: if an option changes the command's output semantics in a way the RTK route cannot preserve, the rewrite is skipped. The result may be less compact, but it still answers the command the user actually issued.

The safe handling of arguments is as important as recognizing operators. A value-taking option can contain a string that looks like another flag. A short option cluster can combine several meanings. Everything after the shell or tool's -- boundary may be a pathspec or an argument forwarded to another program. If RTK removes or inserts its own option by rescanning text rather than by retaining the token position, it can delete a user argument that merely resembles an RTK flag. The registry's typed token information exists to prevent that class of mismatch.

This conservative behavior explains why a long command registry does not imply universal rewriting. RTK can recognize a command family and still decline a particular invocation. Support is conditional on the command's output path, argument grammar, pipeline position, wrappers, and shell structure. The raw result is not evidence that RTK has malfunctioned; it may be evidence that the system correctly identified an unsafe case.

Exclusions, bypasses, and native authority

Users can make the boundary explicit. Configuration can list commands that should never be automatically rewritten, including a whole command or a narrower subcommand such as a Git operation. A single invocation can set RTK_DISABLED=1, which causes RTK to step aside for that command. An already-prefixed RTK command is left alone rather than becoming rtk rtk .... Commands with no matching rule pass through unchanged, and an explicit passthrough route lets users run an unsupported command without forcing RTK to invent a filter.

These controls are useful for more than troubleshooting. A user may need a tool's exact output format, may be debugging the filter itself, or may be working with a command whose output is meant for another program rather than for an LLM. An exclusion is a declaration that the user's need for the original boundary is stronger than the expected context reduction. The system should honor that declaration at the same point where it normally decides to rewrite, including command forms that would otherwise receive a specialized RTK transformation.

Permissions add a separate source of authority. RTK's rewrite registry can answer “is there an optimized form?” but the host decides whether the command may be changed and whether that change may be approved automatically. Deny wins over ask and allow. Ask permits a proposed rewrite while keeping a user prompt. An explicit allow can support automatic approval where the host exposes that capability. With no matching rule, the default remains ask rather than silently becoming allow.

Different hosts cannot all express these outcomes in the same way. One may support an updated command and a distinct ask response. Another may lack an updated-input field and use a deny-with-suggestion pattern so the agent retries the explicit RTK command. A rules-only integration has no interception event at all. RTK preserves those differences because forcing every host into one permission behavior would make the central optimization layer more permissive than the host it is supposed to serve.

The fail-safe contract extends to errors in the hook. If the RTK executable is missing, the incoming data is malformed, a rewrite subprocess fails, or the registry finds no safe match, the hook should not block the underlying command. The original command runs. That protects availability, but it also means a command can execute normally while receiving no compression. Installation status and savings or discovery reports are therefore necessary companions to the hook: they reveal whether the boundary is active rather than assuming that a configured integration intercepted every request.

The central lesson is that RTK's rewrite layer is a set of guarded transitions. It normalizes without discarding execution state, splits only where shell semantics permit, rewrites only where the downstream context remains safe, and defers when it cannot establish that condition. Permission rules and user overrides constrain the same path from outside. A high-savings filter reached through an unsafe rewrite would be a poor result; a raw command that preserves meaning and authorization is often the correct result.

Once a command reaches RTK safely, a second question begins. The registry has decided which operation may run through the proxy, but the output still contains a mixture of evidence and repetition. The next issue is how RTK chooses a compact representation without losing the failures, changes, and status signals that made the command worth running.

References