Compozsh is shell code: installing it gives the tracked bootstrap and enabled peer add-ons access to the interactive shell environment. That trust should rest on inspectable behavior, not on a promise from the maintainer. This document defines the shipped security boundary, identifies every intentional sensitive-data and administrator boundary, and provides repeatable checks for the exact commit you intend to run.
The guarantees apply to all behavior implemented by repository-managed files at the commit being audited. Private add-ons, the machine-local initializer, installed programs, inspected repositories, the operating system, GitHub, and software opened or built at the user's request are independently controlled code or services. This document discloses every class of local handoff from Compozsh to one of those boundaries. No disclosure creates an exception that permits Compozsh itself to transmit data.
All processing performed by Compozsh stays on the machine running its Zsh process, including a user-controlled remote host during SSH. Compozsh never transmits user or project data under any circumstance. There is no telemetry opt-in, consent exception, debugging mode, feature flag, or future product mode that weakens this rule. A feature that requires Compozsh-owned transmission is outside the product contract and must not be added.
This invariant governs Compozsh code; it does not pretend that the operating
system or a program explicitly invoked by the user is offline. If the user asks
Git to clone, fetch, pull, or push, Git communicates with the destination the
user selected; the transparent Compozsh git wrapper adds no data, endpoint,
or separate request. An application opened by an explicit file action, an Xcode
build phase, an installed runtime queried for its version, build tools, and
private add-ons retain their own independently auditable security and privacy
behavior. Compozsh network prohibition and external
boundaries lists the
network-capable handoffs; the feature sections below disclose the remaining
local handoffs (for example Git hooks and filters during explicit switches,
chpwd hooks and external synchronization). The
sections apply this invariant without restating it.
Privacy, credential protection, data minimization, and user control are top-level product goals. Compozsh acquires only the facts required for the visible task, within its displayed scope, and retains them only for the necessary lifetime. Temporary view state stays in memory when practical; every intentional persistent location and lifetime is enumerated below.
Sensitive values are handled as literal, bounded data and are never evaluated
as shell code. That includes Zsh arithmetic, which expands $(...) inside a
subscript: numbers from files, Git, tools, settings, the terminal and the
inherited environment are validated as decimal text before any arithmetic use,
with a digit bound unless the fixed output format of /usr/bin/stat already
bounds the field; names such as paths and branches
never enter arithmetic text; and private state names inherited from the
environment are discarded or read only as text. Compozsh does not collect
information merely because it is available in the environment, filesystem, Git
metadata, command output, or terminal session. It does not accept plaintext
authentication secrets; an operating-system or explicitly chosen external tool
owns its authentication input. Operations use the least privilege and
narrowest exact target their implementation permits.
New collection, persistence, or privilege is not an ordinary implementation detail. It requires an explicit product decision, disclosure of the exact data, purpose, path, trigger, lifetime, access and cleanup behavior, independent audit evidence, and focused regression coverage. Transmission is prohibited rather than configurable. If scope, ownership, permissions, or cleanup cannot be established safely, the operation must fail without broadening access or retaining another copy. External tools, user-owned configuration, and hosting remain separate trust boundaries and are disclosed rather than presented as guarantees made by Compozsh.
- It has no telemetry, analytics, crash-reporting service, account, project server, background daemon, automatic Compozsh software update check, or runtime package download.
- The tracked shell and installer define no project endpoint and initiate no network request. Updates happen only when the user runs Git themselves.
- It does not collect, read, log, store, or transmit a
sudopassword. The two external-media tools and explicitcompozsh --sudo-touch-id enable|disablemodes invoke Apple's/usr/bin/sudo -v;sudoand PAM own the authentication prompt, Touch ID exchange, and timestamp. Compozsh never receives the password or fingerprint data. - It does not use
sudo -S,SUDO_ASKPASS, a password variable, the login keychain, or the macOSsecuritycredential command. - It never reads the clipboard. Explicit Copy actions write only the visibly
selected path or branch, current directory, a bounded Xcode test report,
or complete matching lines from the retained Simulator or device log snapshot
through
pbcopy. The optional website writes a visible installation command only after its Copy button is clicked. - The shell configuration, installer, and static site upload nothing. This includes shell history, paths, Git data, project metadata, file previews, diffs, runtime versions, disk metadata, and installer output.
- It does not automatically download and execute shell code. Installation uses only files already present in the reviewed clone.
These constraints admit no Compozsh exception.
Compozsh creates no off-machine storage or transmission destination. This table is the authoritative list of every intentional Compozsh data location, its lifetime and its limits. Some entries are persistent because a shell and a recoverable installer need local state.
| Data | Location and lifetime | Contents and bounds |
|---|---|---|
| Command history | ${HISTFILE:-${ZDOTDIR:-$HOME}/.zsh_history} by default |
Zsh's normal local, shared history and the in-memory history picker |
| Native completion dump | ${ZDOTDIR:-$HOME}/.zcompdump, written by Zsh's stock compinit (called by .zsh.editor with its full audit, and with -d only for the compiled copy) when a fresh shell finds it missing or its fpath file count or Zsh version changed; persists until deleted, including after --clean and uninstall |
Zsh-generated shell code that compinit sources: the count, Zsh version and the verbatim first-line words of fpath completion files (normally function, command, service, pattern and key names), private entries included, plus completion widget bindings. No fpath directory paths, file bodies, history, environment values or project data. Mode follows the umask at write time (0644 under the default 022); an interrupted write can leave .zcompdump.<host>.<pid>, or zcompdump.<host>.<pid> in the compiled mirror while the compiled copy was in use |
| Private initialization and peers | ${ZDOTDIR:-$HOME}/.zsh.addons; an initializer moved by ZSH_LOCAL_INIT stays at that absolute path (a relative value is ignored) |
User-owned machine setup and extensions loaded by the bootstrap |
| Recovery copies | ${ZDOTDIR:-$HOME}/.zsh-backups/compozsh-* |
Configuration the installer replaced, preserved instead of deleted; a rollback that cannot put an item back keeps it there and prints the path |
| Installer copy staging | ${ZDOTDIR:-$HOME}/.compozsh-install.*, a mode-0700 directory that exists only while install.zsh --copy runs; removed on success, handled failure, and Ctrl-C, hangup or termination signals (residue limits) |
The clone's .zsh.addons tree without support/cache or support/compiled (untracked files included) plus the .managed-by-compozsh marker, staged before any active state moves; never sourced |
| Compiled peer mirror | ${ZDOTDIR:-$HOME}/.zsh.addons/support/compiled, a mode-0700 directory written by install.zsh (both layouts; rebuilt by each installation) and by interactive startup, which creates it when missing, recompiles shipped peers that are new or changed, drops the entry, wordcode and record of a shipped peer that was removed or renamed and, after compinit, compiles a new or changed completion dump; persists until the next installation, --clean (archived with the add-on tree) or uninstall, except that a removed or renamed peer's entry lasts only until the next shell that can compile |
Executable Zsh wordcode: per shipped peer (every regular .zsh.<name> file in the shared add-on directory, tracked or not: the clone's .zsh.addons for --symlink, the managed compozsh/ namespace for --copy) a symlink to its absolute path and the .zwc that zcompile made from it, never private peers in ${ZDOTDIR:-$HOME}/.zsh.addons; for the completion dump a zcompdump symlink to it, its zcompdump.zwc (the dump's names, compiled) and a zcompdump.manifest of the Zsh version, the dump's path, the same parse context and the dump's device, inode, size, mtime and ctime; a manifest of the Zsh version, the parse context it was compiled under (always Zsh's default parse-affecting options and its two built-in aliases, run-help and which-command; never an alias a startup file defined), the shipped add-on directory and each peer's relative path, device, inode, size, mtime and ctime (a - record marks a peer that stays text until it changes); the .managed-by-compozsh marker; only after a compile refused a shell of another Zsh version, a zsh note of /bin/zsh's version, device, inode, size, mtime and ctime; only while a compile runs, a .lock directory and *.compozsh-tmp-<pid> files; and, only when compinit, given the compiled copy's entry, must rewrite its dump, Zsh's own zcompdump.<host>.<pid> and then the rewritten dump at the zcompdump entry, written by stock compdump under your umask (0644 under the default 022) until .zsh.editor moves it to ${ZDOTDIR:-$HOME}/.zcompdump or deletes it (if that step cannot run, it stays there as a regular file, never loaded as the compiled copy, until the next dump compile replaces it with the symlink). It holds only the compiled code of the shipped peers (including any .zsh.<name> file you added to the clone), the completion dump's contents (compiled, or as text in that transient file), the parse context, paths and file identity; never your aliases, environment values, history or project data. Loaded only while fresh |
| Installer compile staging | ${ZDOTDIR:-$HOME}/.compozsh-compiled.*, a mode-0700 directory that exists only while install.zsh runs; removed on success, handled failure, and Ctrl-C, hangup or termination signals (residue limits) |
The new compiled mirror before activation and the superseded one after it; never sourced |
| Optional sudo Touch ID policy | /etc/pam.d/sudo_local until explicit disable; /etc/pam.d/.compozsh-sudo-touch-id.* during enable and after an abnormal interruption |
Three fixed lines enabling Apple's pam_tid, created only by compozsh --sudo-touch-id enable; ACL-free, root:wheel, mode 0444 before publication |
| Prompt, appearance, and picker facts | Shell memory; discarded with the shell or view | Color-scheme classification, runtime versions, Git state, paths, Context trigger/disclosure flags, positive/exclusion fields and their bookmarks, temporary view snapshots |
Direct Git behind /usr/bin/git |
Shell memory until compozsh --refresh or shell exit; replaced when a selector changes |
Only when PATH selects /usr/bin/git: the Git path /usr/bin/xcrun --find git names, its device, inode, mode, owner, size and modification/change times, and the selectors it was resolved under (the PATH choice, exported DEVELOPER_DIR, TOOLCHAINS and SDKROOT, and the developer directory the launcher would use: an exported DEVELOPER_DIR, else the first xcode-select link, else the default places, found with file tests as under Missing developer tools), or a refusal (Apple's Git launcher) |
| Local manual summaries | Shell memory until refresh, discovery-selector invalidation or shell exit | Bounded NAME descriptions and page-name/section attribution from the manual roots; describes literal names, not executable identity |
| Manual background transport | Shell memory, native PTYs and an unlinked kernel FIFO during one request | At most 16 MiB / 8,192 records in 8-KiB reads from one capture process under two supervisors; mode-0600 FIFO in a mode-0700 ${TMPDIR:-/tmp}/compozsh-manual-async.* directory, both unlinked once opened; no regular file of descriptions or selectors |
| Public OS manual cache | ${ZDOTDIR:-$HOME}/.zsh.addons/support/cache/.zsh.manual.cache, until refresh/replacement/removal |
Protected OS manual NAME descriptions, aliases, relative page IDs and a public freshness key, owner-only and inert; .zsh.manual.lock and bounded .zsh.manual.cache.* staging residue. No private selectors or custom manuals |
| Manual formatter directory | Private mode-0700 temporary directory during capture | Empty mandoc working directory; page text stays in bounded memory and pipes, never a file. Removed on cleanup; an uncatchable termination can leave it empty |
| Interaction lens state | Shell memory for the active prompt; replaced as buffer or outcome changes, discarded with the shell | Interaction kind, sanitized bounded excerpts and structural summaries, up to eight 240-character alias previews, context anchors, latest outcome, a bounded autosuggestion prefix; never leading assignment values |
| Draft inspector | Invocation memory, only after Option-Return; released on return | At most 32,768 literal draft characters plus a truncation notice, cursor/length/current-folder facts, reading filters and frames; can display sensitive text, unredacted and unexecuted |
| Captured help and command readers | View-scoped shell memory; the tool catalog is released when its compozsh call or Draft inspector Tools view returns |
Tool catalog: every loaded public add-on function name (no count cap), whether it has a same-source companion, and its sanitized file label under .zsh.addons (private peers included), from Zsh's in-memory function metadata. Tool help: at most 64 same-source companions of 32,768 characters each (direct help: only its companion). History inspector: at most 32,768 characters of the selected command, no second history catalog |
| Command composer | Invocation memory; an accepted draft moves to ZLE or the native buffer stack until edited, submitted or discarded | Literal prefill and fields of at most 4,096 characters each, current folder, generated quoted draft; optional local ref capture for Git revisions. No file, clipboard or custom history write |
| Git Change atlas | View-scoped shell memory; released on return | Path-prefix groups, entry-count bars and bookmarks from the captured file list, staged/unstaged distinct. No discovery, bulk content read, saved atlas or new worker |
| Git Tree / All files navigation | Current review's shell memory; released on review exit | Directory prefixes, folder summaries (change states, child areas, captured numstat) and expansion/filter/scope/selection state from the 1,000-entry/256-KiB list; ancestor choices at most 200 rows/262,144 prefix characters, with a partial notice. No discovery, saved preference or new worker |
| Living prompt receipts | Ordinary terminal display and terminal-owned scrollback | A local HH:MM timestamp and the exact submitted command in each command receipt, plus status/duration in applicable outcome receipts; Compozsh writes no receipt log, and terminal retention follows the user's terminal settings |
| Terminal title | The terminal's window/tab title, replaced at each prompt, after an in-widget directory change and at each command start; display and retention follow the terminal and any multiplexer | user@host: <current folder> (home as ~) at each prompt and after an in-widget directory change, and while a command runs user@host: <command word>: the first lexical word after leading assignments, at most 60 characters. Never an assignment value; the identity alone after an array assignment or when no command word is established. Controls sanitized; written only when standard output is a terminal and TERM is not dumb |
| Prompt tool descriptions | Current-shell memory; cleared on shell exit or invalidation | At most 64 same-source command/help pairs; up to 262,144 characters of source/definition identity; 240-character summaries and option descriptions from at most 4,096 characters per guide plus the Touch ID subguide. No disk cache |
| Git comparison choices and snapshots | View memory, released on view exit | At most 1,000 refs/256 KiB of names, kinds and object IDs; resolved endpoints, paths and bounded diff snapshots. No saved comparison catalog |
| Git branch picker | Invocation memory; catalog released when g returns, details cleared on view exit |
Up to 5,000 local branch names / 512 KiB, current branch first, with a partial notice beyond either bound; ordered by current branch and up to 200 checkout reflog entries, then other refnames. Details at most 262,144 characters across 401 refs, 512 per subject, with inspector text built only for the selected branch. No fetch, saved history or background refresh |
| Known remote branch fallback | Invocation memory until g returns; tracking configuration and a created local branch persist in the repository only after confirmation |
Up to 1,000 remote-tracking ref names and commit IDs / 256 KiB, and 128 remote names / 32 KiB; no remote URLs. No fetch, pull or disk cache |
| Git Working changes refresh transport | One mode-0700 ${TMPDIR:-/tmp}/compozsh-review.* directory with a mode-0600 FIFO, plus screen-scoped worker/provider processes and shell memory |
One framed local status/selected-diff candidate at a time, at most 1 MiB; no log, daemon or persistent review cache |
| Created Git worktrees | Explicitly selected new folder; branch refs and registration in the repository's Git common directory | Created only on g --worktree acceptance; persists until explicit Git/workspace removal, which preserves branches, and survives Compozsh uninstall |
| External device captures | Invocation memory; released when external-device --flash or --format returns |
Image and installer paths and stat identities, an optional user-supplied checksum and computed digests, disk identifiers, media names, sizes, protocols and device-tree paths, the filesystem catalog (each diskutil property list read straight into memory, at most 1 MiB), and the writer's stage, byte counts and native error text that can name the image or device. Its files are the external media temporary files |
| Temporary operation captures | ${TMPDIR:-/tmp} |
USB ISO listings, write and in-session verification progress and image mount points (names and exceptions), bounded Xcode discovery and Build & Run settings output, transient test-result bundles, Xcode skill-export staging (compozsh-xcode-skills.*, Apple-authored skill text), Git syntax transport FIFOs (not source files); validated paths removed on normal and handled-error cleanup |
| Simulator run output | Run-scoped shell memory and two pipes beneath the selected Simulator's data/tmp; released at run exit | Stdout/stderr and unified logs of the exact installed executable: tail and frozen snapshots each at most 32 KiB/200 source lines, 8 KiB unfinished line per source; reader state; launch PID, observed identity, executable and data-directory paths. No log file; native log privacy behavior can expose sensitive app values |
| Explicit physical-device app installation and console | The selected device, under Apple's tools and storage policy; a run-scoped local FIFO and shell memory | devicectl replaces the app's installed copy, which stays after console exit or failed launch. Monitoring keeps at most 32 KiB/200 source lines plus snapshots via a mode-0600 FIFO in a mode-0700 directory, removed on cleanup. No log file |
| Command line opened in an editor | One mode-0600 file under two hard-linked names, ${TMPPREFIX:-/tmp/zsh}XXXXXX and the same name plus .zsh, written by Zsh's stock edit-command-line widget, only on an explicit Ctrl-X Ctrl-E, while $VISUAL/$EDITOR runs (Zsh writes the draft directly into that file); Zsh removes both names when the editor returns, and an abnormal termination can leave both |
The complete draft, including continuation lines, which can hold anything typed on the command line. The editor's own swap, backup, undo or history files follow the editor's settings |
| Exported Apple skills | Detected coding agents' local skill directories | Created only by an explicit xcode --export-skills invocation and marked for safe refresh; per-skill staging and backup siblings and their cleanup limits are listed under Apple skill export |
| Clipboard values | The clipboard of the machine running Zsh; may outlive the view or run under operating-system/user control | Only explicit Copy writes: a path, branch, current directory, visible website command, bounded Xcode test report, or matching Simulator/device log lines that can hold local paths, diagnostics and sensitive app values. Never read back |
Where a row names shell, view or invocation memory, Compozsh keeps that data in
process memory and pipes and writes no file of it; any file, FIFO or directory
is named in the row. Operating-system paging (encrypted swap, the sleep image)
is outside this guarantee. Captured command output, including Git status
paths, branch and worktree catalogs and USB progress metadata, is split into
fields inside Zsh. Compozsh's own code passes no data through here-strings
(<<<), here-documents or =(...) substitution, which Zsh 5.9 implements with
temporary files under ${TMPPREFIX:-/tmp/zsh}; the editor row above is Zsh's
own widget. Process substitution <(...) and >(...) uses /dev/fd pipes on
macOS.
compozsh --refresh clears the current shell's in-memory runtime,
manual-summary, help-description, Git-directory and grep-capability caches and
the direct Git behind /usr/bin/git, and runs rehash. At the next TTY prompt it also requests replacement of eligible
public OS manual cache data for future shells. It neither reloads private
configuration nor updates installed tools or other shell sessions.
The subsections below give, per feature, what is read beyond the table, its limitations, and where to inspect it. Interaction details that carry no data, lifetime or privilege consequence live in the linked engineering contracts. Regression filters are collected in Focused regression checks.
Appearance reads only ZSH_COLOR_SCHEME and the passive COLORFGBG hint
and keeps a light or dark classification (dark when an automatic hint is
absent or invalid). It sends no terminal query, reads no terminal input,
starts no process, and reads no project or private file. Palette defaults and
public overrides live in shell memory; consumers resolve current roles without
separate default palettes, rendering never recreates them, and missing or
non-associative public maps count as unavailable rather than being read as
arithmetic. Completion, printable-stack, help, grep and man colors follow
terminal capabilities and NO_COLOR; manual-page selection contrast uses fixed
indexed-color arithmetic. Reference RGB colors do not establish contrast
for arbitrary terminal profiles. Inspect .zsh.addons/support/.zsh.appearance,
.zsh.addons/support/ui/, and the .zsh.output, .zsh.editor,
.zsh.navigation and .zsh.prompt peers.
Shared screens. The support/ui/ peers own views, input, painting and
screen restoration. Layout, resize, disclosure navigation, rewrapping (with a
bounded source-row map), row spacing, focus expansion and per-paint styles
derive only from captured facts, the current palette and terminal dimensions:
they discover, capture and execute nothing and keep no copy of the user's draft
beyond screen-restoration state. Without an alternate screen, a session keeps
only the display width of each prompt and draft line, measured at entry from
the prompt's % escapes without running its substitutions. The input dock
places the caret through ZLE's PREDISPLAY/POSTDISPLAY, restored by paint,
without putting query text in the
caller's buffer. View state is scoped to the feature callback and the painted
frame to its screen session, released on every exit; secondary menus
and notices scope their own labels and capabilities and return exact action
values; callback names come from trusted feature code, never labels or project
data. Explicit capture and input-idle hooks keep their own limits, final actions
stay feature-owned, and cleanup restores the caller's terminal and editing
state. There is no registry, persistent storage or shared background process; a
feature may use one documented, bounded input-idle child per screen session, as
the Git review transports and the optional resident system-Vim syntax helper do.
See the full-screen standard.
support/ and peer laws. support/ holds maintained implementations
shipped in both installation modes, loaded by ordinary recursive peer
discovery with no added loader privilege, load phase, storage or enforced
read-only permission; personal customization uses documented settings in the
machine-local initializer. Shared callable files declare one first entry point
with exclusive helpers below it and are classified pure (calculation
from supplied inputs with caller-local results), impure (external reads,
mutable shared state, writes, process execution) or UI; these names aid review
but sandbox nothing and do not prove arbitrary input safe. The peer
configuration laws
describe equivalent configured behavior after loading the same peers under the
same prerequisites; they confer no sandbox or permission to replay effects.
Sourcing changes shell memory, and sourcing .zshrc again can rerun the
private initializer. Tests exercise order convergence and re-sourcing; the
website's algebraic model is an illustration, not evidence that arbitrary
add-ons are safe or effect-free.
Context and Interaction lens rendering, the Option-I pin toggle, resize and
line-finish transcript repaint derive from the in-memory prompt snapshot and
ZLE buffer using bounded Zsh lexical structure, without evaluation, expansion,
target resolution or execution. They launch no process, read no
provider/project/runtime/Git/filesystem data and write no file; ordinary
precmd capture is the boundary that reads the local facts in this inventory.
The one other prompt capture repeats it after an explicit in-widget directory
change: when a Browse, Recents, Search or Draft inspector Files action
(Change directory, Enter linked directory) succeeds, the editor runs the
prompt's same fact capture and title update after screen cleanup and before
repainting, so the prompt never describes the previous folder. It keeps the
draft and records no outcome, receipt or history entry. Like precmd, it runs
any PROMPT_PROJECT_CONTEXT_FUNCTIONS a private add-on registered, here
inside the line editor; those functions are that add-on's own code.
This covers lens derivation and repaint, not the complete ZLE pre-redraw hook
chain: the independent syntax highlighter keeps bounded filesystem checks for
literal path tokens without evaluating substitutions or globs. READY keeps
captured toolchain facts visible after acceptance and screen clearing, within
display bounds; this extends visibility, not capture scope or lifetime. Row
vocabulary is specified in the prompt contract.
- Rows labeled
… TEXT, includingENDPOINT TEXT, are sanitized, width-bounded buffer excerpts that do not prove a path, endpoint, query or operation exists. Context rows reuse the latest prompt capture.PIPELINE(FLOW/STAGES) andCHAIN(FLOW/STEPS/CONTROL) summarize literal&&,||,;or&structure;COMMENTusesCOMMENT TEXTand the advisorylikely remain an interactive shell comment;REDIRECTselectsOUTPUT TEXT,INPUT TEXT,DESCRIPTOR TEXTorRESOURCE TEXTfrom the literal operator without opening or validating a target. - Inferred
ACTIONtext useslikely,appears, ormay. Help-derivedACTIONtext instead names documented intent from the bounded same-source help capture. Neither is a prediction, authorization, validation, or completed effect. CAUTIONrecognizes only selected high-confidence forms: it is intentionally incomplete, does not block Return, and its absence is not evidence that a command is safe.- The shipped
ghelper is classified as Git (bareg: local branch workspace;g --review,g --worktreeand help forms: review, worktree or help cues) from literal structure and captured Git context, without callinggor Git. - With AUTO_CD and a lone directory, the highlighter shares only the exact bounded draft and current-folder key of its existing observation for an advisory NAVIGATE cue; it is cleared on the next highlighting pass (including skipped passes), is never reused for a changed draft or folder, resolves no command, and does not guarantee that the directory remains available at Return.
- Leading assignment values are never copied into lens state, although their
names may help describe the command, avoiding a second display of a
potentially sensitive value; the value stays in the real buffer and can enter
the transcript, process arguments and history if accepted. A
SUGGESTIONrow reuses only a bounded visible prefix of editor-owned autosuggestion state matching the exact buffer, with no history search or unseen tail.
Dynamic prompt text has controls sanitized and prompt syntax escaped: %
always; backslash, $ and backticks under PROMPT_SUBST; ! preserved under
PROMPT_BANG. This prevents prompt evaluation or rewriting without changing
the real buffer. The shared display sanitizer replaces C0 controls, DEL and C1
controls (U+0080–U+009F, such as CSI and ST) with ? in every shell locale,
including C and US-ASCII, where it matches a C1 control by its two-byte UTF-8
encoding. A stray 0x80–0x9F byte outside a UTF-8 sequence can remain; Compozsh
relies on the Unicode terminal it requires to decode that byte as invalid
UTF-8, not as a control. The public project-segment extension accepts only a
0–255 color index or a fixed basic Zsh color name; an invalid color falls
back through the validated tool role or native text before reaching prompt
syntax.
A transcript repaint changes only decoration; ZLE keeps the exact submitted
command, and Compozsh keeps no separate command store. Empty and
whitespace-only submissions receive the same receipt without changing their
literal buffer, executing an extra command or adding history. The LAST row is
replaceable shell-memory status for the latest command, including fast
success. Command receipts, plus outcome receipts for failures and slow
successes, become terminal display and terminal-owned scrollback; compaction
neither conceals nor removes submitted command text from the terminal or Zsh
history (see Secrets on the command line).
The terminal title is another
terminal-owned display. At command start, .zsh.prompt's preexec hook lexes
at most 512 characters of the accepted command without evaluation or expansion
and titles only the first word after leading assignments, so it never shows an
assignment value. After an array assignment, without a command word, or
without the shared assignment predicate it shows user@host alone. The command
word itself appears unredacted, so the title is not a redaction boundary
either. Check it with zsh tests/run.zsh 'running-command title'.
Help workspace. Owned --help modes (calling their fixed same-source
companion) and exact compozsh help command requests (resolving the
loaded-function metadata catalog) open a workspace when terminal I/O and the
alternate screen are available. One native pipe captures at most 32,768
characters plus lookahead before interactive input, disclosing truncation; it
reads no project, skill directory, operational tool or private configuration
and launches no pager. At most 128 derived topics plus Complete guide (which
keeps unclassified text within the same limit) hold usage, description, sections
and literal argument explanations in invocation memory.
Navigation, filtering, scrolling and resize call no provider; topic IDs never
become arguments, and prose examples are never executed, inserted or copied.
Pipes, redirects, NO_COLOR, absent UI/help peers and unsupported terminals
get complete printable help. Accents derive at most 128 spans per sanitized
line from literal documentation structure; draft and log readers do not opt
in. The separate Compose example action appears
only with explicit command identity and a _compozsh_template_<command>
capability from the same source as the command and its help companion
(currently g and mkcd); providers are trusted source-owned code returning
fixed recipe IDs, never discovered from files or help prose, and the action
opens the command composer. Other help
paths stay inert. Inspect .zsh.addons/.zsh.help and the public help
branches.
Prompt tool descriptions are captured only at a TTY precmd from loaded
function/source metadata, without filesystem discovery, by calling matching
same-source _compozsh_help_<command> companions; a public command is never
invoked with --help. Each definition is capped at 65,536 characters. The pipe
reads one character past its 4,096-character guide bound and discards an
incomplete final line; control-bearing or overlong descriptions are omitted.
Changed or removed definitions and compozsh --refresh invalidate the
help-description snapshots; identity data stays in shell memory and is never
written. In both help paths, companions are trusted static documentation
providers under the add-on contract: output bounds neither sandbox a
misbehaving machine-local companion nor impose a wall-clock limit.
Edit-time descriptions and alias previews read only those snapshots, current definition identity and bounded alias definitions. They name documented tool/option intent, not target resolution or argument validation; compound and danger cues take priority, and stock alias descriptions require the exact current definition. Ordinary, global and suffix aliases preview at literal command positions, including pipelines, chains and after leading assignments; global aliases also match literal arguments and redirection targets, and a trailing-space alias enables the next word's preview. Within 512 draft characters and 64 lexical tokens, at most eight definitions are retained, each capped at 240 characters with an omission marker. Quoting, escaping and native alias suppression are respected; here-document operators stop inspection, and nested substitutions are not inspected. Definitions are never evaluated, recursively expanded or cached separately. Previews are sanitized, prompt-escaped and width-fitted; they can show sensitive text already in an alias to someone viewing the terminal and are not credential redaction.
Draft inspector. Option-Return opens it explicitly. Unlike passive prompt disclosure, its Read action can show the literal draft, including leading assignment values; no token is evaluated, expanded, resolved or executed, and the full draft stays in native editing/screen-restoration state. Help discovery and read-only Git capture happen only after their named choices; Git uses the displayed current folder and ignores draft flags and paths. Files and History receive ownership only after the inspector closes. Only explicitly accepted insertion replaces the draft (History inserts the original full command); cancellation and read-only return preserve the original command and cursor.
Command composer. Simple prefill is never expanded or evaluated;
unsupported or complex drafts keep the ordinary inspector. A pure builder
applies native Zsh quoting to literal fields around fixed command/option
tokens; preview and filtering perform no external reads. Fields are displayed
literally and can contain sensitive values, so this is not a secret-entry or
redaction mechanism. A Git endpoint field may explicitly acquire the current
folder's repository and safe local refs through the review providers, within
the same 1,000-ref/256-KiB bounds; only the submitted endpoint is validated,
and a pinned local commit ID is returned. No
network, checkout, whole-draft validation or command execution occurs; without
ref browsing, literal entry remains possible without promising a valid
revision. Only Replace draft exports: it verifies that the current folder
still matches the displayed scope, unwinds the screen, and inserts via ZLE
BUFFER or print -zr into the native buffer stack for the user to submit. It
adds no clipboard, file or history write; normal acceptance can record the
submitted command as usual. The editor's leading-space prefix is carried over,
so composition does not silently remove a HIST_IGNORE_SPACE preference.
Escape exports nothing and restores the original cursor and bookmarks.
Inspect .zsh.addons/.zsh.compose, the same-source
template companions in navigation and tools, and the editor/help handoffs.
The runtime functions in .zsh.addons/support/functions/ run only at the
prompt's fact capture (precmd, or after an in-widget directory change).
Loading them defines functions and shell-memory data only: it inspects no
project, probes no executable, installs no toolchain and registers no
background task. Numeric comparison consumes supplied strings, separately
from metadata observation. README's Project and runtime context section
documents the sources and precedence. Besides simple
version files and .tool-versions, readers recognize literal Rust toolchain
TOML, Go go.work/go.mod minima, Swift's first-line tools minimum, Cargo's
package Rust minimum and Python's project version range. No manifest is sourced,
evaluated, passed to a package manager or used to install or activate software.
Metadata paths must resolve inside the detected project, be regular files, not
be leaf symlinks, and pass a 1 MiB eligibility size check. Non-following/
nonblocking opens, descriptor type validation and mode-specific byte budgets
also reject leaf replacement with symlinks/FIFOs and bound reads of a growing
file. Simple selection files and Swift's tools declaration need a complete first
line (newline or observed EOF) within a 4 KiB read budget. TOML and multi-entry
sources use a 64 KiB read/parse budget (at most one extra byte to detect
growth), 256 lines and 4 KiB per line, rejecting excess work before interpreting
declarations. An incomplete first line, a source over a parsing budget, a
selector over the 240-character display cap (displayed requirements are
prompt-sanitized and escaped), an ambiguous value or unsupported syntax is
unverified, not inferred compatible. A file rejected by the initial path/type/1
MiB check is skipped and a later eligible source may supply a requirement; this
neither validates the skipped source nor establishes compatibility with all
project requirements. These checks are not an atomic snapshot of a concurrently
changing tree. Captured text stays in invocation memory; the installed-version
cache is shell memory, refreshable with compozsh --refresh, and disappears
with the shell. Missing runtime support leaves project identity/tool markers.
Without Apple's developer tools, a runtime whose probe would run one of
Apple's /usr/bin launchers shows as not-installed and runs nothing
(missing developer tools). On macOS,
/usr/bin/java is Apple's stub for an installed JDK; without one it fails and
may offer to install one. When the Java probe would run it (found on PATH,
directly or through a link), the prompt first looks, with file tests only,
for a runtime the stub could start: an exported absolute JAVA_HOME that
holds an executable bin/java other than the stub itself (not
JAVA_HOME=/usr, nor a bin/java linked to the stub), any visible entry in
/Library/Java/JavaVirtualMachines or
$HOME/Library/Java/JavaVirtualMachines, or
/Library/Internet Plug-Ins/JavaAppletPlugin.plugin. With none, Java shows as
not-installed, nothing runs and the answer is never remembered, so the next
prompt after an install probes it. Without running anything,
ls /Library/Java/JavaVirtualMachines ~/Library/Java/JavaVirtualMachines '/Library/Internet Plug-Ins/JavaAppletPlugin.plugin'
shows what counts: an entry listed under either folder, or an existing
plug-in, means the prompt runs the stub, as does such a JAVA_HOME (inspect
_prompt_runtime_java_present in
.zsh.addons/support/functions/.zsh.impure.prompt_runtime_version).
Probing installed runtimes is an external
boundary. Inspect without
executing: git grep -n . HEAD -- .zsh.addons/support/functions/.
The optional .zsh.manual peer captures local manual NAME descriptions once,
in the background, at the first TTY precmd; source time captures nothing and
the editor does not wait. The producer contract
specifies the mechanics.
- Sources. Literal MANPATH, relative entries resolved at capture (without
empty fields it skips default and configuration discovery; empty fields insert
defaults); conventions for the first 128 absolute PATH entries and documented
fallbacks; literal MANPATH/MANCONFIG directives in
/etc/man.confand at most 16 selected configuration files, 8 KiB each, never evaluated, formatter settings and other directives ignored; and at most 16 absolute, control-free SDK/platform/developer/toolchain roots of at most 4,096 characters from one fixed/usr/bin/xcode-select --show-manpathsquery, which honorsDEVELOPER_DIRand is validated against 64 KiB (conventional roots remain without it). At most 64 deduplicated roots and 16 validatedMANSECTentries apply. Capture can read manuals in user-selected project folders, but not private shell peers, the initializer or history. - Bounds. At most 4,096 entries, 64 KiB per page, and 8,192 retained
name/summary pairs of 240 characters. Symlinks and whole-page
.soaliases can reach installed manuals outside a root, with at most eight reads per chain, through non-following/nonblocking opens and regular-descriptor checks (not an ancestor-race sandbox). Fixed native gzip and, for formatting-heavy NAME sections, fixed native mandoc on captured stdin each process at most 64 KiB in and out under a one-second child CPU limit, not a wall-clock deadline. mandoc ignores command/file-writing requests and refuses absolute/parent includes, and its private empty working directory blocks relative includes of the caller's folder; this is native formatting, not an OS sandbox. Corrupt, unreadable or unresolved pages fail quietly; pages without usable NAME text keep a manual-available notice. Noman,manpath,whatis, index generator or documented command runs, and no manual text becomes shell code. - Use. ABOUT text is inert, prompt-escaped and describes a literal name, not executable identity or argument effects; aliases and functions do not inherit it. Typing and resizing consult only the snapshot and loaded metadata; bounded descriptor callbacks may merge a completed candidate without discovery or cache I/O. Custom, third-party and developer manuals never persist; only eligible OS descriptions use the public cache.
- Transport. The bounded request identity (PATH, MANPATH, MANSECT, locale, developer selectors, configuration base, source generation, and PWD only for relative roots) stays in memory; neither selectors nor fingerprints are saved. A candidate replaces the visible map only if generation and identity still match, framing, schema, count, every record and END validate, the worker succeeded and no bytes trail; failed or obsolete candidates never partially apply. Each callback reads at most 8 KiB.
- Ownership. Two native Zsh supervisors isolate the ordered capture process
and its descendants from the user's job table, coprocess and event handlers;
only fixed function names become
zptycommands. Refresh, re-source, selector change, pre-execution cancellation and shell exit release them. A 30-second deadline stops collection; cancellation allows 100 ms of cleanup, then terminates the owned group. After a deadline an idle outer owner keeps its identity, without collecting or polling, until the parent handles the failure; it is not a daemon. Another same-account process can still kill or alter supervisors. Without async support capture is synchronous; failures use the ordinary fallback with no retry loop until explicit refresh. - Residue. Uncatchable interruption can leave an empty setup directory/FIFO, the empty formatter directory or a public cache staging file; no transport document body is ever a regular file.
All Compozsh performance caches use the configuration base's
.zsh.addons/support/cache/ (following ${ZDOTDIR:-$HOME}, never the project
or the source peer's symlink target). The sole producer, .zsh.manual, writes
.zsh.manual.cache, an empty .zsh.manual.lock and temporary
.zsh.manual.cache.* files. No private, user or project facts or their
fingerprints belong there; history, the native completion
dump and recovery storage stay separate. Format,
locking and digest mechanics are in the
producer contract.
- Eligibility. Only discovered
/usr/share/manpages whose whole resolved symlink/.soforwarding chain stays in that OS tree, on root-owned regular descriptors without group/world write. This relies on Apple's protected system tree; ownership or a conventional path elsewhere is insufficient. Persistence also requires macOS and readable fixed OS-version metadata; other platforms stay uncached. - Contents. Only relative system page IDs, NAME aliases, descriptions and a format/version key; never page bodies, custom documentation, discovery paths, selectors or negative lookups. The inert format is bounded (4,096 records, 32 MiB; page IDs up to 512 characters, descriptions up to 240, at most 16 aliases of 255 characters) and rejected before loading on controls, NUL, truncation, duplicates or malformed fields. It is never sourced or evaluated, bootstrap discovery excludes the directory, and valid owner-controlled prose is still untrusted advisory text, not executable identity, authorization or a target.
- Freshness. The key is a
/usr/bin/shasumdigest, run in an empty environment with a fixed PATH, of the owning implementation code, Zsh version, an accepted installed locale and fixed OS-version-file metadata; it detects identity changes but does not authenticate data. Capture still discovers pages: removed pages disappear, new eligible pages extend the cache, other documents stay memory-only, and unknown commands cause no edit-time reads. In-place OS manual edits without an OS-version-file change needcompozsh --refresh, which requests fresh capture at the next TTY prompt and replaces eligible results for future shells; refresh also invalidates the help-description snapshot with the other in-memory caches. Other open shells keep their snapshots until refreshed. - Storage safety. Ownership and POSIX modes without group/world write are validated from the resolved configuration base through add-ons/support/cache; directories are owner-only, and files must be regular, owner-only, singly linked and not symlinks, checked with non-following/nonblocking opens and descriptor checks. Extended ACLs are not inspected or removed, so these checks neither authenticate the cache nor establish exclusive access under a custom ACL, and they do not sandbox another process controlling the account or ancestor replacement above the configuration base. Unsafe state is refused, not repaired; missing, stale, corrupt or unwritable storage keeps full uncached features.
- Publication and residue. Same-directory atomic rename publishes complete snapshots under a time-bounded kernel advisory lock; lock failure falls back to uncached capture. The empty lock file can remain, and an uncatchable kill can leave owner-only staging residue (handled failures remove it). The snapshot persists until removal, refresh or replacement, and removed public records may remain within its bound until replacement.
- Removal. Deleting the cache directory removes every module's cache and
residue; later eligible capture recreates it. Git ignores generated caches.
Installers exclude source caches, preserve destination caches on normal
updates and archive them with the old add-on tree on
--clean; uninstalling that tree removes active caches, while recovery archives keep their own retention. User-selected synchronized or network-mounted configuration storage is an external boundary; Compozsh adds no cache service.
Independently read .zsh.manual's eligibility, key, load/store and capture
helpers, .zshrc's cache exclusion and install.zsh's staging exclusion. Its
fixtures exercise inert data, private-input exclusion, freshness, failures and
concurrency.
.zsh.editor runs Zsh's stock compinit once per fresh shell; re-sourcing
skips it while _comps exists, and if the initializer or an earlier private
peer already ran compinit, that call owns the dump instead. Without -C,
-i, -u or -D, compinit keeps its full compaudit ownership and
permission check of fpath (an interactive prompt can exclude insecure
entries or abort). Its dump is ${ZDOTDIR:-$HOME}/.zcompdump, resolved when it
runs and with no Zsh-version suffix; compinit gets -d only for the compiled
copy below, and _comp_dumpfile names that usual path afterwards either way.
Compozsh adds no fpath entry and leaves completion's use-cache style off
(so it writes no .zcompcache).
- Contents. A
#files: <count>andversion: <zsh>header; the_comps,_services,_patcomps,_postpatcomps,_compautosand_comp_assocstables;zle -Candbindkeylines for every underscore-named completion widget that exists whencompinitruns (including any the initializer or an earlier private peer defined), withzmodload -i zsh/complistwhen one needs it; andautoload -Uzlines.compinitreads only the first line of eachfpathcompletion file and copies its words verbatim. Normally they are function, command, service, pattern and key names, and names from privatefpathentries (for example Homebrew's site-functions or a personal directory) are recorded too; a private file that declares a command by path records that path, and the rest of an#autoloadline becomes dump code, so text infpathfiles is trusted like their function bodies. The dump holds nofpathdirectory paths, file bodies, history, environment values, host name or project data. - Rewrite and lifetime. On a fresh shell,
compinitsources the dump as shell code when its header matches the currentfpathscan and Zsh version, and rewrites it only when it is missing or either differs; editing a completion file without changing the count keeps the old dump until it is deleted. The file persists across shells, installs,--cleanand uninstall, andcompozsh --refreshdoes not touch it. Deleting it loses nothing: the next fresh shell that runscompinitregenerates it, at the cost of one fullfpathscan. - Mode and residue. Zsh's
compdumpwrites.zcompdump.<host>.<pid>in the same directory and renames it into place, so each rewrite applies the shell's umask at that moment (0644 under macOS's default 022, 0600 under 077); Compozsh neither tightens nor checks the mode. Stockcompdumpinstalls no cleanup, so any interruption between that write and the rename (for example Ctrl-C while a first shell starts) can leave the staging file, whose name carries the host name and process ID. An unwritable configuration base skips the dump, and completion still works from the scan. - Trust.
compauditchecksfpathdirectories and files, not the dump. It sits in the configuration base beside the bootstrap, never in the current directory or a project, so an account that can replace it can already replace.zshrc. Editing it in place depends on its own umask-derived mode: with group or other write, other accounts that can reach the configuration base could change codecompinitsources, so keep a umask without group or other write (the macOS default 022 qualifies). Zsh's.would load a.zcompdump.zwcno older than the dump if another tool created one; Compozsh never creates one. - Compiled copy. After
compinit, a dump that is a regular file with no fresh record is compiled by.zsh.addons/support/compile.zshin Apple's/bin/zsh -ffrom/(the same rules, lock, atomic renames and fallbacks as the compiled peer mirror; a dump written in the current second waits for the next shell) intozcompdump.zwcbesidezcompdump, a symlink to the dump, with azcompdump.manifestholding only the header, parse context and the dump's identity.compinitis then given that symlink as-d, so Zsh's.reads the wordcode, only while the mirror passes the peer checks, the manifest names the running Zsh and this dump, the dump's device, inode, size, mtime and ctime equal the record, and the parse contextcompinitsources it in (its ownemulate -L zsh, then the alias tables) equals the compiling shell's; any other case loads the text dump, and a dump over 512 KiB or one that fails to compile stays text until it changes. The header check andfpathscan are unchanged: when they differ,compinitrewrites its dump at the symlink's path, and.zsh.editorhas the same script rename that new dump onto${ZDOTDIR:-$HOME}/.zcompdumpat once (or delete it if the rename fails, so the next shell rewrites the dump itself). Its staging filezcompdump.<host>.<pid>left by an interruption is deleted by a later compile once a minute old.
Independently read the call and Zsh's own implementation:
git grep -n -e compinit -e compdump -e zcompile -e fpath -e use-cache \
-e cache-path -- .zshrc install.zsh templates '.zsh.addons/**'
zsh -fc 'print -rl -- ${^fpath}/(compinit|compaudit|compdump)(N)'
ls -l "${ZDOTDIR:-$HOME}/.zcompdump"The first command should list only .zsh.editor's autoload -Uz compinit
line, its compinit -d call for the compiled copy and bare compinit call
with their comments and its _zle_compdump_state and _zle_compdump_compile
helpers, the two commented fpath lines in templates/init.zsh and
.zsh.addons/support/compile.zsh's zcompile of shipped peers and the dump
into their mirror with its zcompdump names and
comments;
the second prints the stock functions
(on macOS under /usr/share/zsh/5.9/functions/) whose _comp_dumpfile
default, header check and compdump writer establish the facts above. The
third should show your account as owner and no w for group or other; if it
shows either, delete the dump and choose a umask without group or other write
before the next shell. These reads cover the call and format, not every
completion function's runtime behavior.
Matching. The pure matching_* entries under support/functions/ compile
literal queries (keyword fragments in any order) and filter caller-supplied
captured text into caller-local results. They read no provider, UI state,
filesystem, command metadata or history and create no persistent cache or saved
filter; feature collectors keep their ranking, duplicate policy and capture
limits. Exclusion rejects candidates matching one case-insensitive literal
phrase before result limits, without reading file contents. Query punctuation is
literal, operators are never evaluated, and decimal result limits are checked
before arithmetic. Both fields and their bookmarks are view-local. Refinement
performs no discovery; exclusion-only filesystem/Git capture requires explicit
submission and keeps the source bounds. Spotlight requires positive filename
text and never falls back to a walk. Missing matching support selects
native/plain fallbacks. In pickers, plain digits always edit a filter, including
its first character; acceptance requires Enter or an explicit shortcut, and
Option/Meta-digit shortcuts apply only a visible slot with both filters empty.
Effects. The compozsh_effect_* entries own explicit clipboard writes and
file Open/Reveal. Each receives the exact caller-selected value and captured
executable path, checks executable availability, and rechecks an absolute file
target before opening it. Clipboard payloads go to standard input literally,
without an added newline; the helper reads no clipboard and keeps no payload
after return. UI and result-model code never calls effects; callers dispatch
only after screen cleanup, or directly for explicit commands such as cpdir.
Without an effect entry, its workspace capability is omitted and direct
requests fail safely. Loading and re-sourcing invoke no command and need no
peer order.
Path + Tab captures immediate directory entries for a lone path or an
explicit directory argument such as vim ~/Developer. The command prefix stays
in invocation memory; insertion replaces that argument after screen cleanup
without executing the command, and Escape and Copy preserve the draft. Browsing
reads entry names and file-type metadata, never contents, and releases snapshots
and prefix on return. Quoted or escaped leading tildes, including lone
command-position paths, delegate to native completion, so quote removal cannot
redirect a literal path to HOME. Inspect .zsh.addons/.zsh.editor.
Once a bracketed-paste opening prefix is recognized, the shared picker keeps
input ownership through the closing marker. A stalled or oversized paste is
discarded in the existing screen, keeping only a bounded marker suffix during
recovery; Enter, cancellation bytes and handled SIGINT cannot release a delayed
payload into the command buffer. The modal screen owns SIGINT abort through
terminal cleanup and restores prior function, static or ignored handlers on
return; recovery temporarily intercepts SIGINT in that scope while resize
handling stays active. If the terminal omits the closing marker, the recovery
screen tells the user to press Escape and type [201~, after which normal
cancellation resumes; a closed input stream exits recovery without a busy retry
loop. The ordinary short Escape-key window applies before a paste prefix is
recognized, and complete non-paste CSI keys consume a bounded suffix (at most 32
characters after ESC [ 2) without entering recovery. Recovery also pauses
enclosing Git result consumption and live log draining; retained data stays
bounded, and a timed-out Git check is reported when normal input resumes. See
Keyboard and ZLE.
The optional syntax helper uses a private system-Vim child and mode-600 FIFOs inside a mode-700 temporary directory. Captured source travels through those pipes, not regular temporary source files. On timeout or screen exit, cleanup closes descriptors, kills and reaps the owned child when still live, retires its completed shell-job record, and removes the transport paths. It does not signal unrelated running or suspended jobs or detach a live helper.
The Git Working changes auto-refresh worker is reaped the same way. A worker
that has published its result or exited is reaped without a signal. Pause,
manual refresh, timeout, an invalid or oversized response and screen exit send
TERM only while its job is running or suspended; the worker then kills and
reaps its own Git provider before exiting. Either way the parent retires only
the worker's completed job record.
Xcode reaps its discovery supervisor, Run log observer, device console and LLDB output drainer the same way once its own ownership-checked stop has ended the child: each reap leaves unrelated completion records in place and retires only that child's completed job record.
Zsh 5.9's wait and jobs builtins scan prior completed records when NOTIFY
is off, even with an exact operand. These cleanups therefore share one narrowly
scoped MONITOR-off, NOTIFY-on section and restore caller options afterward.
This avoids that broad scan and keeps already-pending unrelated completion
records.
Limitation: an unrelated job that newly finishes during this short section
can still have its completion notification retired by native Zsh handling;
preserving every concurrent notification is not guaranteed. See the native
implementation.
Prompt capture only reads native job states: running and suspended jobs count;
completed records do not. It never lists, waits for, or acknowledges jobs to
update the indicator. jobs -l can report a previously killed process and
acknowledge that completed record; listing it does not kill the process.
Xcode device console polling recognizes suspended jobs without blocking on them, and cleanup preserves failures after resuming a stopped child. If Zsh cannot distinguish an exit status of 127 from a missing cached status after suspension, cleanup conservatively keeps the earlier nonzero stop status. Manual capture supervisors are stopped and reaped as their section describes.
File review. All files, Tree, Jump to ancestor and the Change atlas derive every view, folder summary, count and filter result from the captured Git list and existing commit/comparison numstat facts (summaries for retained result rows only), including deleted prefixes and distinct staged/unstaged entries. Navigation, folding, scope changes and filtering (including hidden descendants) perform no provider read or directory enumeration, and folders request no diff or syntax data. Opening a file uses the bounded diff providers and safety checks, including the failed-preparation block on uncached reads. Filtered or partial coverage, scope and refresh limits stay visible. Wrapped path displays are presentation, with no redaction guarantee or clipboard action. Refresh reconciles navigation with the new list, keeps the file bookmark as its target, and invalidates bookmarks whose numeric identities belonged to the old list. The atlas keeps the original numeric file/change-kind identities and commit/comparison IDs, stops pending Working changes refresh work until return, and its nested Back returns to the previous filter/selection and review position. Working files may change after capture; no view promises an atomic snapshot. See Git workspaces.
Confirmations. Action cards, their captured target/plan columns and Xcode
configuration-return bookmarks add no provider or execution boundary.
g --discard-all prints complete captured status before confirmation and may
add a scoped native confirmation screen with at most 32,768 status characters
in its inspector. Only literal y or Y confirms; empty Return and
cancellation refuse. Screen restoration precedes the revalidation and writes
described under Local-only Git controls; plain and
missing-UI confirmation remains. No new privilege, network path, persistence,
automatic action or rollback claim is introduced.
Branches. The local catalog includes names absent from the bounded checkout
reflog. It is one bounded read; beyond its bounds the picker labels itself
partial, filtering covers captured branches only, and the remote fallback
stays off because a partial catalog cannot show that a name is not already a
local branch. Opening the g branch picker also reads git remote names and
for-each-ref refs/remotes/ once, before the first filter, through the same
local-only provider controls, so filtering never calls Git; those refs are
offered only when nothing local matches, and every later filter reuses that
snapshot.
These are locally known remote-tracking refs, not a server result or freshness
guarantee: a missing branch requires an explicit user-run g fetch <remote>
before reopening g, and typing, selection, confirmation and creation never
fetch. Confirmation shows the exact source and new local branch and defaults to
Back. After terminal cleanup, a non-forcing
git switch --track=direct --create revalidates
the selected ref's locally available commit and refuses a moved or deleted tip
or an existing local branch, with transport, lazy fetching and submodule
recursion disabled. As with any explicit Git switch, configured checkout
filters and post-checkout hooks remain external programs with their own effects
and network behavior, unsandboxed by this workflow. Git can fail after partial
checkout work; inspect its diagnostic and the repository before retrying, as no
automatic rollback or cleanup is attempted.
Reads. The workspace reads local refs, commit IDs, registered paths and flags, directory identities, and explicitly browsed parent-folder names. Removal also reads operation markers, tracked-tree modes, index flags and status including ignored and untracked names (Git may read working file contents for status); creation reads the selected committed tree to check for submodules; moving reads operation markers and committed-tree modes and validates source/destination directory identities without scanning contents. Captures stay in invocation memory, bounded to 256 KiB each and 1,000 worktrees, branches or child folders per catalog; failed or oversized safety captures refuse the action. The committed-tree mode and index-flag checks, whose listings grow with the repository, instead stream Git's NUL-terminated output through a pipe in 64-KiB chunks, holding at most one chunk and one partial record, and keep only whether a submodule or a non-ordinary index entry appears, so repository size alone never refuses them; a Git failure, or an empty, unterminated or longer-than-one-chunk record, still refuses. Compozsh writes no worktree catalog or navigation history. Git may take longer than these output bounds suggest on slow storage.
Git policy. Every workspace Git call disables transport, lazy fetch, hooks,
fsmonitor, automatic maintenance, optional index writes and submodule
recursion, and explicit roots override inherited Git
directory/index/object/namespace selectors. Entering a worktree changes this
shell's directory and can run independently owned user chpwd hooks.
Create writes the chosen new folder and native Git refs/registration; the
installed Git and the user's umask govern checkout ownership and modes.
git rev-parse --path-format=absolute --git-common-dir resolves the metadata
base: registrations live under its worktrees/<id> directory, and branches in
the repository's native reference storage. No ignored/private files are copied
from another checkout and no access controls change. Configured
clean/smudge/process filters or required-filter settings, including globally
configured Git LFS even where a checkout might not use it, are refused rather
than invoked or disabled; submodule checkouts are refused. Creation runs
worktree add --no-checkout, rechecks effective configuration in the new
branch/directory (covering conditional includes absent from the source), then
populates the complete committed tree with a non-forcing
read-tree -m -u --no-sparse-checkout. Refusal at that stage leaves the branch
and registered empty folder for inspection; no filter runs.
Move / rename calls native git worktree move after review of the exact
source and destination; tracked edits, untracked and ignored files travel with
the folder, its registration is updated and the branch is kept. The destination
must be absent and on the same filesystem; main/current, locked, missing,
in-progress and submodule checkouts are refused. Moving runs no checkout or
filter, copies no file, changes no privilege and leaves the shell's directory
unchanged.
Remove deletes only the confirmed registered linked checkout and keeps its branch. It refuses main/current, locked, missing or detached worktrees, in-progress operations, changes, untracked or ignored files, submodules, sparse/unmerged indexes and assume-unchanged flags. The removal chooser filters the captured catalog and reads file status only for the selected target, sharing confirmation and validation with selected-worktree options. Repository and directory identities, branch/commit and safety checks are revalidated after screen restoration. No force, branch deletion, automatic stash, recursive shell deletion or rollback is used; there is no Undo.
Limitations. Validation followed by Git is not an atomic filesystem
transaction: concurrent writers or path replacement can change a move or
removal outcome, so stop other writers first. Git errors keep their status. A
failed move can leave location and registration partially updated; an
interrupted or failed creation can leave a branch, registration or partial
folder. Inspect git worktree list, git branch and the exact paths before
retrying; Compozsh performs no automatic rollback or residue deletion. A failed
directory change after creation preserves the checkout. Removing Compozsh
leaves created worktrees and branches intact.
Interaction details are specified in the Xcode workspace contract.
Captures. Provider stdout keeps at most ZSH_XCODE_CAPTURE_MAX_BYTES
(262,144 by default, minimum 4,096; the Build & Run settings capture keeps
5,242,880 bytes when that limit is smaller, as under Run targets) and
diagnostics at most 8,192 bytes, enforced while the command runs, including
bytes in temporary files; readers hold one 8-KiB chunk while discarding
excess. Two-byte completion records identify finished synchronous streams;
incomplete captures and stdout overflow are rejected unparsed. Streams stay
separate, the native status is kept within the limit, and byte bounds impose
no duration limit. Cleanup removes the private capture directory. Scheme and destination discovery streams through three
mode-0600 FIFOs in a mode-0700 directory into the same budgets, writing only an
atomically published exit-status record of at most four bytes. After exit,
queued output is consumed without waiting for inherited writers, and at most
eight 8-KiB chunks per stream per poll keep a busy provider from monopolizing
input. A supervisor keeps the provider group's identity until cleanup; owned
child state is checked before signaling the group, the supervisor is then
reaped, and completion, Escape and Ctrl-C remove the FIFOs and record. Discovery reads no terminal stdin and never starts during paint or
resize; its loading label is view-scoped, and later captures keep their own
status and cancellation guidance. Snapshots and selections stay bounded to four
schemes in the open workspace; canceled or removed destinations never become
implicit selections.
Tests. Test and Rebuild & Test use a transient result bundle with verbose
test diagnostics disabled; Xcode can still store test-authored attachments and
logs there. Compozsh reads only size-bounded summary/detail JSON and source
locations, never attachments or source files, rejects a symlinked bundle, and
deletes the whole bundle before showing results. An uncatchable termination can
leave it under the identifiable compozsh-xcode-test.* temporary directory.
Run targets. Build & Run and Rebuild & Run keep the captured destination's
platform, device ID, architecture and variant. Build settings are bounded to 100
targets and 5,242,880 bytes (about 100 targets at Xcode's roughly 42 KiB each),
or ZSH_XCODE_CAPTURE_MAX_BYTES when larger; up to that many bytes sit in the
private capture files while the command runs and in invocation memory until
product resolution returns, and a larger capture fails unparsed with guidance
naming the bound. Product paths and validated bundle identifiers stay in
invocation memory. Several qualifying products require an explicit choice, no
filesystem search infers one, and a symlink leaf or vanished product is
rejected before handoff, not atomically. A Mac app opens as a new instance
through native open, with the chosen architecture and no process monitor; a
command-line product runs in the restored terminal with native output and
status. App code has its own access and network behavior.
Physical devices. devicectl receives the selected app and device ID and
reaches that device over its configured wired or wireless connection, an
independent tool boundary like explicit Git transport. Installation replaces the
installed copy and launch replaces the running instance, with the captured
architecture. Compozsh adds no network client, endpoint, broad device scan,
automatic connection policy, provisioning update or device log file;
pairing/trust, Developer Mode, signing and provisioning stay with Xcode and the
user. A failed build or installation prevents launch; a failed launch does not
undo installation. --console owns stdout, waiting and catchable signal
forwarding. Monitoring sends the owned child's output to a private FIFO and the
bounded reader without bridging stdin; plain fallback keeps the native console
and scrollback. Stop restores the screen, signals the still-owned child, drains
for at most one second, then forces exit with a notice to check the device; a
forced console end cannot guarantee that the app stopped. The child is reaped
and its FIFO/directory removed; an uncatchable termination can leave a
compozsh-xcode-device.* directory whose FIFO holds no log. Console messages
may contain sensitive app values.
Simulators. Run reads only the selected device's SIMULATOR_ARCHS,
SIMULATOR_RUNTIME_VERSION and SIMULATOR_SHARED_RESOURCES_DIRECTORY through
bounded simctl getenv captures. A captured architecture is requested at boot
and passed explicitly to runtime 26 and newer; older runtimes must report only
that architecture. Missing, malformed, incompatible or ambiguous output stops
before the viewer, installation or launch, and a running Simulator is never
shut down to change architecture. The data directory must be a normalized
absolute control-free path whose tmp child exists, without symlink leaves; a
fresh mode-0700 data/tmp/compozsh-xcode-run.* holds two mode-0600 FIFOs that
the app reaches as /tmp/compozsh-xcode-run.*/output, a path simctl
resolves in the device's data filesystem, and cleanup reuses the
captured host path. --terminate-running-process replaces the running bundle
and binds its new process to this run's output FIFO;
SIMCTL_CHILD_NSUnbufferedIO=YES is exported only for the launch and the
log-stream spawn, so only the app and its log process get NSUnbufferedIO=YES;
the shell, ps, LLDB, stopping and log privacy are unaffected. Launch metadata
uses the temporary capture boundary; app output never
serves as a PID or command. ps reads the launch-returned PID's user, start
time and executable identity, rechecked before attaching or stopping; this
cannot be atomic against exit or replacement. The window opens through the
explicit Device Hub or Simulator bundle of the Xcode chosen by
xcode-select --print-path (honoring DEVELOPER_DIR); Device Hub receives a
local devices:///manage/select URL with only the validated device ID, so
another app's URL registration cannot choose the viewer, and a failed open
prevents installation and launch. Apple's viewer is an explicit
external-application boundary.
Unified logs. xcrun simctl spawn runs the device's
log stream --level debug --style compact --color none with a native predicate
matching processImagePath to the installed CFBundleExecutable path, so
filtering happens before records reach Compozsh. In-app framework records are
included; helpers and extensions with other executables are excluded; another
launch of the same executable can match. The observer starts before launch
with a bounded readiness wait, but native drops and startup races prevent
exhaustive capture; records can appear in both sources, and merged order is not
timestamp order. Only the log child gets LOGRC=/dev/null, so personal
.logrc rules cannot broaden capture. Compozsh never enables private-data
logging or changes log privacy settings, and native behavior is no redaction
guarantee: a disposable iOS 27 Simulator exposed a probe's synthetic .private
payload by default. Captured logs may therefore contain sensitive app data,
including values marked private, a limit distinct from Compozsh's local,
ephemeral handling. Compozsh writes no app-output log and does not control the
Simulator's own log retention.
Reading and copying logs. The input loop drains at most 32 KiB per idle
turn, fairly across sources, sanitizing display; Read wraps at most 20,000 rows.
A noisy app can see temporary pipe backpressure. A failed source is disclosed
and stops while the other continues; a closed pipe does not mean the app exited.
Read is a full-width view of the retained tail. Search, exclusion, severity
view, counts, formatting and copies derive only from the displayed snapshot,
without new log discovery, while the run keeps draining; following, pausing,
filters and bookmark persist for the run, and Options and the guide hold
displayed text while capture continues. Matching keeps source order and
duplicates; display controls are sanitized while filters and copies keep raw
lines and metadata. Message words never set severity, and Error/Fault counters
cover recognized native records, filtered ones included, without establishing
app health. Copy all, Copy filtered and Ctrl-Y copy complete matching raw lines,
including offscreen text, without labels or wrapping, but not the run's whole
history; empty output has no Copy action. Options freezes its scope, direct
Ctrl-Y uses the current displayed capture, and acceptance freezes the payload
without fetching newer output. The captured pbcopy path is rechecked and
invoked only after screen restoration, without reading the clipboard; Run
reopens with feedback while the app continues, and a clipboard failure makes the
final status nonzero. Copies may contain the sensitive values above and outlive
the run, including through clipboard
synchronization.
LLDB and cleanup. Enter LLDB gives the restored terminal to the
Xcode-selected debugger for that PID with automatic .lldbinit and
symbol-script loading disabled; LLDB may read the app's memory, symbols and
source, and its commands, macOS authorization and the app are separate trust
boundaries. Colors use only fixed native settings and validated numeric palette
values for this invocation: no debugger configuration or color plugin, no
interpretation of app output or input, and NO_COLOR or non-color output
disables them without relaxing the autoload protections. Nothing elevates
privileges or changes signing. The log observer lives only within the run and
is stopped and reaped before LLDB and on cleanup: each signal revalidates the
owned child, a suspended child is resumed for termination, and an observer
still alive after one second of bounded draining is killed and reaped without
masking the app's or debugger's status. During LLDB a temporary Zsh child
discards app output so the app does not block. LLDB exit, Stop, Escape and
handled errors stop the still-identical app, reap children, close descriptors
and remove the FIFOs and directory. A changed or unavailable identity prevents
termination and reports a failure telling the user to check or stop the app in
Simulator, which does not prove the app is alive; failed stops are reported.
An uncatchable termination can leave an app, observer, drainer or pipe
directory; Simulator can stop the app, and there is no recovery log to retrieve
or uninstall.
Apple skill export. xcode --export-skills detects local coding-agent
installations and the selected exporter. With interactive UI it first shows
destination and exporter paths in a scoped plan (context bounded to 32,768
characters, truncation disclosed) before staging; Escape writes nothing, and
Export skills closes the screen before exporting and installing each skill.
Noninteractive, dumb and missing-UI calls run immediately. The plan is not a
dry run or transaction: personal conflicts stay preserved, and a later failure
can leave earlier skills updated. Rendering performs no provider read, project
discovery or private skill-directory scan; mode help is inert. The exporter
writes Apple-authored skill text into one mode-0700
${TMPDIR:-/tmp}/compozsh-xcode-skills.* staging directory. Each skill is then
built in a sibling .xcode-skill-stage.* directory inside the agent skill
folder, and a refresh moves the previous managed copy into a sibling
.xcode-skill-backup.* directory until the new copy is active. Normal and
handled-error cleanup removes all three, except that a failed rollback
deliberately keeps .xcode-skill-backup.*/previous and prints its path.
Ctrl-C still removes the ${TMPDIR:-/tmp} staging directory, but interrupting
installation can leave a .xcode-skill-stage.* or .xcode-skill-backup.*
directory in an agent skill folder, and an uncatchable termination can leave
any of the three. A backup can hold the previous managed skill (an interrupted
refresh can leave it there instead of in place), so inspect it before removing
it. Inspect .zsh.addons/.zsh.xcode;
zsh tests/run.zsh 'Xcode skill' checks the staging name and cleanup, and
zsh tests/run.zsh 'Xcode rollback' the retained recovery backup.
Bare external-device presents two fixed task choices without reading disks or
images; choosing one closes that screen before its normal scoped capture. USB
review and the flash and format modes keep their separate exact-device
confirmations (Administrator boundary). Shared pure
USB readers validate supplied SHA text and native progress records without
consulting caller-local variables or reading devices; raw execution uses that
shared digest validation, while media-specific preflight, checksum requirements
and final verification stay with their effect owners. Progress byte counts,
installer percentages, and diskutil TotalSize and DeviceBlockSize are
validated as decimal text of at most 18 digits before arithmetic, and progress
stage names are compared literally, so this text never reaches arithmetic as an
expression or an out-of-range number. Installer candidate labels
reuse captured metadata, but action-time identity checks acquire fresh
observations. USB and Xcode scalar
extraction share one fixed /usr/bin/plutil invocation over supplied document
text, without provider discovery or persistent state. Xcode child/failure
counts and display-span offsets are validated as bounded decimal text before
arithmetic (offsets use local whitespace splitting), without reading or
modifying caller variables; missing count support keeps neutral results within
the existing traversal and retained-failure limits. Inspect
.zsh.addons/.zsh.usb, .zsh.addons/.zsh.help, .zsh.addons/.zsh.tools and
.zsh.addons/.zsh.xcode.
Compozsh compiles the shipped peers, which are every regular .zsh.<name>
file in the shared add-on directory, tracked or not (the clone's .zsh.addons
for --symlink, the managed compozsh/ namespace for --copy; never private
peers in ${ZDOTDIR:-$HOME}/.zsh.addons), and Zsh's
completion dump, into
${ZDOTDIR:-$HOME}/.zsh.addons/support/compiled. Each peer gets a symlink,
named like the peer, to the peer's absolute path, and beside it the .zwc
wordcode that zcompile produced. One shipped file,
.zsh.addons/support/compile.zsh (not a peer: discovery never sources it),
does all compiling, always in Apple's /bin/zsh -f, run by that absolute path
(never a zsh found on PATH) from /, under Zsh's default options
and only its two built-in aliases, run-help and which-command, with a 077
umask, so the directory is mode 0700 and the files it writes owner-only. Even
zsh -f reads /etc/zshenv, so the file first removes every alias that
defined and writes nothing if it cannot; no startup file's alias reaches the
mirror.
zcompile only parses a peer; it never runs it. Three callers run
that file, Compozsh's only writer of the mirror (compozsh --refresh neither
checks nor rebuilds it):
install.zsh, through the same/bin/zshrun from/, builds a complete new mirror in a${ZDOTDIR:-$HOME}/.compozsh-compiled.*staging directory and swaps it in; an existingsupport/compiledwithout the.managed-by-compozshmarker is refused before any change. It waits for the clock to pass a just-written peer's ctime, at most about 2 s in total; a peer whose ctime is still ahead of the clock then (the clock moved back, or a network volume's server clock runs fast) gets no entry or record and loads as text until a shell compiles it once the clock has passed it (zsh tests/run.zsh 'peers ahead of the clock').- Interactive startup, before any peer loads, when a discovered shipped peer
has no record matching its current metadata (new, edited, or after a Zsh
update), a record names a peer file that no longer exists (removed or
renamed), or the mirror is missing. It passes only those peers; their paths
come from discovery or the manifest, never from the current directory.
Once the new manifest is in place, it deletes every symlink and
.zwcunder the mirror's.zsh.addonsthat the manifest does not name, so a removed or renamed shipped peer leaves no entry, wordcode or record. The mirror must be a real directory owned by you with no group or other access and holding a regular-file marker; a missing one is created (withsupport/if needed, only inside an existing add-on directory), and anything else at that path is left untouched while every peer loads as text. The work runs under a.lockdirectory; a shell that finds a live lock loads text without waiting, and a lock older than a minute is treated as abandoned. New wordcode and the manifest are written to*.compozsh-tmp-<pid>names and renamed into place, so a half-written file is never used, and leftovers from an interrupted compile are deleted by the next one. A peer edited in the current second is left for the next shell, no new peer starts compiling after about 300 ms, and a peer over 512 KiB, or one that fails to compile, gets a-text record so later shells do not retry it until it changes. Startup prints nothing about any of this; every failure means text. Its standard input is/dev/null, so it cannot consume typed-ahead input. It runs/bin/zsh, Apple's Zsh, by that absolute path with/as the working directory, never azshfound onPATH(which could be a version-manager shim that resolves the start directory's policy, or a project's own binary), and compiles only when that Zsh reports the running$ZSH_VERSION. Otherwise it compiles nothing and records in the mirror'szshnote this/bin/zsh's version, device, inode, size, mtime and ctime; while the note still matches/bin/zsh, a shell of another Zsh version starts no compile child and loads text, and so does every shell when/bin/zshis missing (zsh tests/run.zsh 'no compile child'). .zsh.editor, right aftercompinit, for the completion dump only, under the same mirror rules, lock, bounds,/bin/zshrun from/,zshnote,/dev/nullinput and silent fallbacks, as its compiled copy describes.
The one other writer is Zsh's stock compdump. When compinit was given the
compiled copy's zcompdump entry but must rewrite its dump (the fpath file
count or Zsh version changed), it writes zcompdump.<host>.<pid>
in the mirror under your umask (0644 under the default 022; the mode-0700
directory still keeps other accounts out) and renames it over the entry.
.zsh.editor then has compile.zsh move that file to
${ZDOTDIR:-$HOME}/.zcompdump, or delete it if the move fails. If that step
cannot run, the rewritten dump stays at the entry as a regular file under your
umask. It is never loaded as the compiled copy, because the fresh check
requires the symlink, and the next dump compile replaces it with the symlink.
The bootstrap sources a peer through its mirror entry, so Zsh runs the wordcode, only when all of these hold; otherwise it sources the peer's text as it would without a mirror:
- the mirror is a real directory owned by you with no group or other access,
with a regular-file marker, and its
manifesta regular file owned by you without group or other write; - the manifest names the running
$ZSH_VERSION, so a Zsh upgrade means text until the peers are recompiled; - the mirror entry is a symlink to exactly the discovered peer path;
- the peer's device, inode, size, mtime and ctime equal the values recorded
when it was compiled. A peer is compiled only after the clock has passed its
ctime and dropped if any value changed meanwhile, and no account can set
ctime, so any later write,
chmod, replacement ortouchthat restores an older mtime puts that peer back on text until it is recompiled. Zsh's own check (andzrecompile), a.zwcnot older than its source, would accept same-second edits and restored mtimes. The values are read again just before each compiled peer loads, so a peer changed while earlier peers load (by one of them, or by an edit in the same second as its compile) loads its new text, and one removed meanwhile fails to load rather than running the wordcode left behind (zsh tests/run.zsh 'changed while earlier peers load'); the wordcode a shell runs is the text the peer held at that last check, and only a write racing it, as with any file read while it is being written, can still meet the code compiled from the text it replaces. The check relies on the file system recording this machine's clock, as local volumes do; - the parse context matches the compiling shell's when peers start loading (exactly Zsh's default parse-affecting options and its two built-in aliases, unchanged by any system or personal startup file, the initializer or a re-sourced bootstrap), and no peer loaded as text has changed it since. While a manifest exists and the context does not match, startup does not compile either.
So the mirror can change startup time and the source path Zsh records for each
function a compiled peer defines (functions_source names the mirror entry),
never the rest of the resulting shell state; tests compare the complete state
apart from those paths with and without it in both layouts. Discovery
never treats a .zwc file or anything under support/compiled as a peer, and
Git ignores .zsh.addons/support/compiled/ (like support/cache/) and, at
the clone's root only, .zsh_history*, .zsh_sessions/, .zcompdump* and
.zsh-backups/, so a shell whose ZDOTDIR is a clone never offers the
mirror's absolute paths or compiled completion names, its default history
file (and its interrupted-write residue), Terminal.app's per-session history
and state, its completion dump (with interrupted-write residue) or the
installer's recovery copies to a commit
(zsh tests/run.zsh 'Git ignores the compiled mirror'). A
.zwc placed beside a peer by hand is Zsh's own mechanism and outside this
check: Zsh's source uses it while it is not older than the peer.
Residue. An uncatchable termination (SIGKILL, power loss) during a
startup compile can leave the .lock directory, which the next compile
after a minute removes, and *.compozsh-tmp-<pid> files, which are never
used and which the next compile deletes; an interrupted compinit rewrite
through the mirror can leave zcompdump.<host>.<pid>, which a compile deletes
once it is a minute old; a rewritten dump whose move-back step could not run
stays at the zcompdump entry as a regular file, never loaded as the compiled
copy, until the next dump compile replaces it with the symlink. The entry,
wordcode and record of a shipped peer that was removed or renamed remain
until the next shell whose parse context allows compiling (and that finds no
live lock), or until the next installation; --clean archives whatever is
present. Deleting the whole mirror is always safe: the next shell rebuilds it.
Trust. The mirror is executable configuration with the same trust as the
add-on tree it lives in: an account that can replace it can already replace
private peers. Its access checks read ownership and POSIX mode bits only:
extended ACLs on the mirror, which it can inherit from support/, are not
inspected or removed (Zsh has no native way to read them, and startup starts
no process just to check them), so an ACL that grants another account access to the
mirror gives it the same access as one on the add-on tree. Inspect it (ls -e
also lists any ACL), and check every entry independently of Compozsh's own
code, with:
ls -laeR "${ZDOTDIR:-$HOME}/.zsh.addons/support/compiled"
sed -n '1,6p' "${ZDOTDIR:-$HOME}/.zsh.addons/support/compiled/manifest"
zsh -fc 'zmodload -F zsh/stat b:zstat; m=${ZDOTDIR:-$HOME}/.zsh.addons/support/compiled
b=${${(f)"$(<$m/manifest)"}[3]#base }
for l in ${${(f)"$(<$m/manifest)"}[7,-1]}; do f=(${(ps:\t:)l}); [[ $f[1] == - ]] && continue
zstat -H s -- $b/$f[1]; [[ "$s[device] $s[inode] $s[size] $s[mtime] $s[ctime]" == "${f[2,6]}" ]] &&
r=fresh || r=stale; print -r -- "$r $f[1] -> $(readlink $m/.zsh.addons/$f[1])"; done
l=(${(f)"$(<$m/zcompdump.manifest)"}); zstat -H s -- ${l[3]#dump }
[[ "$s[device] $s[inode] $s[size] $s[mtime] $s[ctime]" == "${(j: :)${(ps:\t:)l[7]}}" ]] &&
r=fresh || r=stale; print -r -- "$r dump -> $(readlink $m/zcompdump)"'The last command compares file identity only. It lists every compiled entry
as fresh (used while the running Zsh version and parse context also match
those its manifest records, which the second command shows for the peers) or
stale (ignored; the next shell recompiles it) with its link target, which
must be the shipped peer or, for the dump, ${ZDOTDIR:-$HOME}/.zcompdump.
zsh tests/run.zsh 'compiled peers' checks discovery, freshness, fallbacks,
state equality, startup recompilation, locking, interrupted compiles, the size
bound and the installer lifecycle;
zsh tests/run.zsh 'compiled completion dump' checks the dump's freshness,
fallbacks and rewrite at its usual path.
The installer never prints the contents of an old .zshrc or private add-on,
but a recovery backup can contain secrets that were already present there.
Protect and eventually archive or remove those backups according to your own
retention policy; Compozsh deliberately does not delete them automatically.
Ctrl-C, a hangup or a termination signal during installation rolls back like a
handled failure, and further such signals are ignored while it cleans up.
Rollback moves an archived item back only into a free location, so an item it
cannot restore stays in the .zsh-backups/compozsh-* directory, whose path it
prints. Copy mode first stages the shipped add-ons in a mode-0700
${ZDOTDIR:-$HOME}/.compozsh-install.* directory, which the bootstrap never
sources and cleanup removes. An uncatchable termination (SIGKILL, power loss),
or a signal that stops mktemp between creating that directory and reporting
its name, can leave it behind, and an uncatchable termination can also leave a
partially applied installation whose replaced files remain in the backup.
Earlier releases named the staging directory .my-zsh-install.*. Every
installation also builds the compiled peer mirror in a
mode-0700 ${ZDOTDIR:-$HOME}/.compozsh-compiled.* directory, which holds the
superseded mirror after the swap and is removed the same way, with the same
uncatchable-termination limits. Inspect
install.zsh; zsh tests/run.zsh 'installer' checks rollback after failures
and signals, the staging name and its cleanup.
An uncatchable process termination or system failure can leave a temporary
capture behind; its validated compozsh-* name makes it identifiable in
${TMPDIR:-/tmp}. For Git review this can be an empty mode-0700
compozsh-review.* directory containing its mode-0600 FIFO; provider output is
streamed through pipes and is not persisted there. Normal exit removes both;
after an uncatchable termination, the user may inspect and remove the
identifiable directory. Other feature-specific residue (manual transport and
formatter directories, cache and completion-dump staging files, Xcode test
bundles, Xcode skill staging and its agent skill-folder siblings, device and
Simulator run directories, interrupted worktree creation,
external media captures and image mount points, and Touch ID policy
temporaries) is disclosed in its own section; the Ctrl-X Ctrl-E draft file,
which does not use a compozsh- name, has its own inventory row.
The locations above are local process, filesystem, agent-directory, and operating-system clipboard interfaces on the machine running Compozsh. A user can independently configure a history, configuration, temporary, or agent directory on a synced or network-mounted filesystem. macOS can also synchronize its clipboard when the user enables that operating-system feature. Compozsh does not configure, detect, start, or control either form of synchronization. Users who require physical single-machine retention must choose local, nonsynchronized paths and disable operating-system clipboard synchronization or avoid Copy actions.
Shell command lines are a poor place for passwords, tokens, or private keys:
they can be exposed through history, process listings, logs, or the invoked
program itself. HIST_IGNORE_SPACE is enabled, but a leading space is only a
convenience and not a secret-storage mechanism. Prefer the macOS Keychain or a
dedicated secret store, and rotate any credential that was exposed. Ctrl-X
Ctrl-E hands the whole draft to your editor through a temporary file (see the
inventory). History autosuggestions show the
unaccepted rest of a matching recent history entry beside the draft; accepting
a line removes it before Zsh repaints that line into scrollback, on
continuation and nested lines and without the prompt peer too, but a line
interrupted with Ctrl-C stays on screen as painted.
Only the Interaction lens and
the terminal title, both secondary presentations, omit assignment values; the
living prompt transcript deliberately retains the exact submitted command text.
Both behaviors are presentation choices, not a
privacy or redaction boundary.
No administrator access occurs at shell startup, during installation, while
showing help, during compozsh --sudo-touch-id status, or during normal prompt,
search, history, Git, and navigation features. Administrator access has two
explicit boundaries: external-device --flash and external-device --format
after the user selects a whole external physical disk and types the exact
visible ERASE diskN confirmation; and
compozsh --sudo-touch-id enable|disable after the user names that
state-changing mode.
- Compozsh captures and validates an external whole-disk identity.
- The user reviews the exact
/dev/diskNtarget and confirms it by name. - Compozsh runs
/usr/bin/sudo -v. Apple'ssudoreads any password directly from the terminal and manages its own authorization timestamp. - Compozsh revalidates the target before mutation.
- Privileged operations use
sudo -n, which refuses to prompt or read a password. The permitted implementations are/usr/sbin/diskutil, one fixed internal Zsh raw-device routine (the only place Apple's/bin/ddruns with privilege), and an Apple-signature-validatedcreateinstallmediafrom the explicitly selected macOS installer application.
A write never targets the disk holding its own source. Before drive capture,
_usb_image_source_disk_capture in .zsh.addons/.zsh.usb resolves the
selected media's filesystem device with /bin/df and diskutil info, follows
an APFS volume to the whole disk of every physical store behind its container,
excludes those whole disks from the drive list, and refuses them again before
any effect when the write starts. Media on a whole disk that diskutil reports
as virtual (a mounted disk image, sparsebundle or AppleRAID set) is refused
before any drive is captured, because macOS does not report the drives behind
it; copy it to a regular drive first. A device, physical store or whole disk
that cannot be resolved is refused too. Media on a filesystem whose device is
not a local /dev/disk node, such as a network share, maps to no local disk
and needs no exclusion. zsh tests/run.zsh 'USB source exclusion' checks these
cases against recorded diskutil answers.
Apple signature validation never consults the requirement a signer declares
for its own code. _usb_codesign_is_apple and
_usb_codesign_is_apple_application in .zsh.addons/.zsh.usb pass fixed
requirements to /usr/bin/codesign --verify --strict -R: the
Contents/Resources/createinstallmedia that runs as root must satisfy
anchor apple (Apple's own code), and the enclosing Install macOS <name>.app
must satisfy
anchor apple or (anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */),
which also admits Apple's Mac App Store signature. Both checks run when the
installer is selected, again after administrator authorization, and again
after the drive is prepared and immediately before createinstallmedia starts;
the last two also compare the installer's and tool's captured device, inode
and times and the tool's size. An installer whose signature no longer
validates, including a genuine older copy whose Apple certificate has expired
or whose chain is not trusted, is refused, because codesign then cannot
establish that Apple signed it; download a current copy from Apple instead.
Check an installer yourself with
codesign --verify --strict -R '=anchor apple' "/Applications/Install macOS <name>.app/Contents/Resources/createinstallmedia"
and, for the application, the second requirement in the same form (expected: no
output, status 0; a refused object prints
code failed to satisfy specified code requirement(s) with status 3, or
another codesign error). zsh tests/run.zsh 'USB Apple signature boundary'
ad-hoc signs copies of /usr/bin/true in a temporary directory, without the
keychain or network, that declare anchor apple, a Developer ID-shaped or a
Mac App Store-shaped requirement or sit under a folder named anchor apple,
plus an ad-hoc signed Install macOS Example.app, and checks that each is
refused while Apple's /usr/bin/hdiutil passes both requirements. No hermetic
fixture carries a genuine installer signature, so the tests do not show that a
real installer is accepted.
The raw-image routine passes image paths, captured source fingerprints, device
names, sizes, and verification flags as literal arguments. Its privileged
routine opens the source once with no-follow semantics, verifies the held file
descriptor's device, inode, size, and modification time against the capture,
and uses that descriptor for writing and read-back comparison. It does not pass
shell history, environment dumps, credentials, or network destinations.
Password handling remains entirely inside the operating system's sudo
process.
external-device keeps its captures in invocation memory, released when the
flash or format task returns. Each diskutil … -plist answer (external disk
list, disk or volume info, filesystem catalog) is read straight into memory
through the shared bounded reader, and output above 1 MiB is refused after at
most 1 MiB + 5 bytes are read. The exceptions are these
/usr/bin/mktemp names directly beneath ${TMPDIR:-/tmp}, created as mode
0600 files or 0700 directories owned by the invoking user. Privileged
output reaches them only through redirections opened by that user's shell; no
sudo command receives a temporary path. zsh tests/run.zsh 'USB invocation state'
checks that the only _USB_* parameters at load are fixed limits, and that
every _USB_* parameter, REPLY, reply and the picker's selection and
bookmark (which can hold filter text) are back to their load-time values after
leaving --flash or --format at drive selection, after a failed write and
after a completed format.
| Name | Created during | Contents and bounds |
|---|---|---|
compozsh-usb-iso.* |
ISO classification | The selected ISO's tar -tf path listing, at most 16 MiB + 1 byte |
compozsh-usb-inspect.* |
ISO classification when that listing is inconclusive | Read-only, nobrowse hdiutil mount point. While attached it presents the image's file tree with the mount's own permissions (observed 0755 with an ISO 9660 fixture; check with stat -f %Lp on the attached mount point), so other local users can read it only through a traversable parent such as /tmp; the default per-user macOS TMPDIR is owner-only. The mount path itself is visible to them in mount and df output |
compozsh-usb-progress.* |
Raw image write, including flash-verify read-back inside the same privileged session |
Standard error of that session: dd write and read-back progress and record counts, fixed compozsh-write-* stage and result markers, and native error text that can name the image or device. No size cap on disk; read in steps of at most 1 MiB |
compozsh-createinstallmedia.* |
Apple createinstallmedia |
Its combined standard output and error. No size cap on disk; ignored above 1 MiB |
flash-verify reads the drive back only inside the privileged write session,
before that session releases the device. _usb_execute treats a write without
that session's compozsh-write-verified evidence as an incomplete-evidence
failure and never rereads the drive afterwards; flash-only skips read-back.
Each of these names is removed by an always block. The ISO listing
(compozsh-usb-iso.*) and the inspection mount point
(compozsh-usb-inspect.*) use the block of the function that created them.
The progress files, compozsh-usb-progress.* from _usb_write_image and
compozsh-createinstallmedia.* from _usb_createinstallmedia_run, use the
block of the shared worker loop _usb_worker_progress_run, which the creator
calls immediately after mktemp. Each block removes the exact path mktemp
returned, on normal return, handled failure or an interrupt that unwinds Zsh,
after checking that the name sits directly in ${TMPDIR:-/tmp} and starts
with its own compozsh-usb-iso. or compozsh-usb-inspect. prefix or, in the
worker loop, with either progress prefix, compozsh-usb-progress. or
compozsh-createinstallmedia.; the loop leaves any other name it is handed in
place. An interrupt between a mktemp call and the start of that block can
leave the name; for the progress files that window includes entering the
worker loop. Progress views first wait for their
native worker to exit, because a command started with & while the MONITOR
option is off ignores SIGINT (man zshmisc, SIGNALS). A second interrupt
during that wait skips the rest of the worker loop's always block: the
privileged worker keeps running after external-device returns, its progress
file remains, and the outer cleanup can request diskutil eject while the
write continues. The mount point is removed only after hdiutil detach succeeds;
when detaching fails, the image stays attached read-only there, detaching is
retried before the next inspection and when external-device --flash returns,
and a further failure leaves both in place without a separate notice. An
interrupt while hdiutil attach itself runs unwinds before Compozsh records the
device: the empty directory's removal then fails if macOS completed the
attachment, which stays mounted there with no retry or notice (find it with
hdiutil info).
Neither the USB peer nor the shared screen it runs in traps SIGHUP; the
screen session installs only TRAPINT and TRAPWINCH. A hangup (closing the
terminal or losing an SSH session), SIGKILL, crash or power loss therefore
skips that cleanup and
can leave any of these names with the contents above; a worker that outlives the shell can keep writing to its progress file
until it exits. Confirm the traps with
grep -nE '\btrap\b|TRAP[A-Z]' .zsh.addons/.zsh.usb .zsh.addons/support/ui/.zsh.ui.zle_picker_screen_session
(only that screen session's comments, LOCAL_TRAPS, TRAPINT and TRAPWINCH
lines).
List residue in Zsh with
ls -ld "${TMPDIR:-/tmp}"/compozsh-(usb|createinstallmedia)* (no
residue: no matches found, status 1) and hdiutil info (no residue: no
compozsh-usb-inspect.* mount point); release an attachment with
hdiutil detach /dev/diskN, then remove its empty directory with rmdir. Read
the creation and cleanup sites with
grep -n -e mktemp -e 'rm -f' -e rmdir .zsh.addons/.zsh.usb. The rmdir call is
in _usb_mount_cleanup, which the inspection's always block calls, and the
progress files' shared rm -f is in _usb_worker_progress_run, away from both
of their mktemp calls.
zsh tests/run.zsh 'USB temporary' uses stand-ins for diskutil, hdiutil,
dd and createinstallmedia to check removal after success and handled
failure, and each name and mode while it exists, for every name except
compozsh-usb-iso.* (removal only; tar has no stand-in seam); the
diskutil stand-in sees no file at all. It also checks that the worker loop
leaves another compozsh-* name in place.
zsh tests/run.zsh 'ISO inspection' covers the failed-detach
retry and a later refused inspection.
zsh tests/run.zsh 'external-media temporary' fails when a
command /usr/bin/mktemp call in .zsh.addons/.zsh.usb lacks a literal
compozsh-* name or when that name is missing from this section. It pairs
calls and names by count rather than per call and does not recognize other
mktemp spellings.
- Compozsh verifies macOS and an ACL-free, root-owned, non-writable
/etc/pam.d. Enable additionally requires the root-owned, ACL-free systemsudopolicy to includesudo_localbefore its requiredpam_opendirectory.sopassword fallback, and the matching system template to advertisepam_tid. Policy files observed above 64 KiB are rejected before reading; this is a fail-closed size check, not atomic isolation from root. statusreads fixed PAM-directory metadata and the fixed target.enableproceeds only when/etc/pam.d/sudo_localis absent.disableremains usable if the enable prerequisites later drift, but proceeds only for a regular, ACL-free,root:wheel, mode0444, byte-identical managed file. Existing custom, symbolic-link, special-file, changed, or unsafe policy is preserved.- The state-changing modes run
/usr/bin/sudo -v. Before enable, this normally means a password; after enable, Apple's PAM stack can present Touch ID. Authentication data remains insidesudo, PAM, and macOS. The localpam_tidresult is scoped to sudo; Compozsh receives no fingerprint, template, or reusable biometric token and introduces no network request. Either authentication method can refresh sudo's ordinary credential cache; a process already controlling the same terminal session may benefit until expiry.sudo -kinvalidates the current timestamp. - After visible authorization, enable repeats platform validation and absence
checking. One
sudo -nfixed internal/bin/zsh -dfcroutine repeats the fixed system-policy checks, creates a root-only temporary file beneath/etc/pam.d, writes only the fixed policy, strips inherited ACLs, appliesroot:wheelownership and mode0444, verifies exact metadata and bytes, and uses/bin/linkto publish exactly/etc/pam.d/sudo_localwithout replacing any path. It removes the temporary link and verifies the published inode. Disable's one fixed privileged routine revalidates type, numeric link count, ownership, mode, ACL and bytes immediately before/bin/unlink. An exact managed target with additional hard links remains removable, but only the literal/etc/pam.d/sudo_localname is unlinked; every other link is preserved for separate inspection. Handled failures do not broaden either target. The privileged routines do not trust the caller's inspection path: they repeat checks against literal/etc/pam.dtargets. A concurrent root policy manager can still replace state around shell-level checks; Compozsh fails closed when it observes drift but does not claim compare-and-unlink isolation from root. pam_tidis configured assufficient. A failed or unavailable biometric attempt falls through to the verified later requiredpam_opendirectory.soauthenticator; the rule changes the authentication method, not who is authorized for sudo.
compozsh --sudo-touch-id is the sole public entry, with status as its default.
Its --help path is static and remains available without the operation peer;
malformed arguments fail before dispatch. The retired command is not an alias.
New policies name the grouped command in their removal comment. Status and
disable also recognize the exact earlier three-line policy whose comment names
the retired command. The privileged removal routine independently validates
either fixed byte sequence, including its exact size, owner, mode and ACL; a
similar or edited comment is not ownership proof. Nothing rewrites existing PAM
files during loading or this command migration. The fixed temporary filename
prefix remains unchanged so interrupted-operation residue stays identifiable.
The managed policy persists across shells and macOS updates until explicit
disable. No backup is created because existing sudo_local policy is never
changed. An abnormal or unhandled termination during the short privileged
enable step can leave a root-owned .compozsh-sudo-touch-id.* file beneath
/etc/pam.d; it is not included by sudo and may be empty, partially written,
or complete, with mode 0600 or finalized mode 0444. Inspect and remove such
residue as administrator after confirming its fixed path and contents. Apple's
pam_tid cannot present Touch ID in SSH's non-graphical session and may also be
unavailable through a multiplexer or another remote context. The later password
authenticator can prompt only when sudo has usable terminal input.
git grep -n 'sudo' -- .zshrc install.zsh '.zsh.addons/**'
git grep -nE 'sudo[[:space:]]+(-S|--stdin)|SUDO_ASKPASS|pbpaste|/usr/bin/security' \
-- .zshrc install.zsh '.zsh.addons/**'The first command identifies operations in .zsh.addons/.zsh.usb and
.zsh.addons/.zsh.sudo-touch-id, grouped dispatch and static documentation in
.zsh.addons/.zsh.help, and a literal advisory in .zsh.addons/.zsh.prompt.
Only the first two peers invoke sudo. The second should print no matches and
return status 1. Inspect every changed result rather than treating the command
as a permanent allowlist. In the Touch ID peer, confirm every privileged target
is a literal path beneath /etc/pam.d and that only /usr/bin/sudo -v can
prompt. On a supported Mac, independently inspect the active system files for
Apple's documented interface with
ls -lde /etc/pam.d /etc/pam.d/sudo /etc/pam.d/sudo_local.template,
sed -n '1,120p' /etc/pam.d/sudo, and
sed -n '1,120p' /etc/pam.d/sudo_local.template; do not paste these files into
a shell command. Apple's macOS Sonoma enterprise notes document the persistent
sudo_local interface, and Apple's published pam_tid source documents its
local graphical-session and askpass limitations.
Compozsh defines no project endpoint and initiates no network request. Living
prompt disclosure and transcript transitions use only the literal editor buffer
and current captured snapshot; they do not evaluate the buffer, poll, run a
background worker, or recapture providers as the user edits, toggles the lens,
or resizes the terminal. A REMOTE or remote-related ACTION row is advisory
text and does not create a connection. The compiles that interactive startup
and .zsh.editor may start run Apple's /bin/zsh -f by that absolute path
from / (compiled peer mirror), never a zsh
resolved through PATH, so these compiles start no version-manager shim or
project directory policy.
Compozsh's Git inspection does not request clone, fetch, pull, push, or
another remote operation.
- Prompt status disables repository-configured clean/process filters, filesystem monitors, hooks, required-filter enforcement, and lazy fetches.
- Branch snapshot providers disable lazy fetches, prompts, transport and
optional writes while reading local refs, reflogs and detail subjects, so a
missing promised object cannot start transport during branch details. They
also disable commit-signature verification (
log.showSignature), so no configured verifier program such asgpg.programruns while reflogs are read. Branch switching separates options from the exact selected ref name, including a ref beginning with--. - Git review applies its own equivalent read-only filter boundary and disables commit-signature verification, so commit history and root-commit documents never run a configured verifier program or mix its output into captured commit IDs.
- One local-only Git entry. Prompt status, filter-name discovery, branch
snapshots, Search's Git discovery, Git review (manual and automatic), the
worktree workspace's captures and operations, and every
g --discard-allread, cleanup, restore and verification reach Git only through one shared support entry,_compozsh_git_local. Every call it makes disables optional locks, lazy fetches, terminal prompts, transport protocols, the pager, replacement objects, filesystem monitors, the untracked cache, hooks and commit-signature verification; each feature only adds its own controls. Without the entry these features run no Git command. Only transparentgandgitarguments and confirmed branch switches or creations call Git directly. - Apple's Git launcher. When
PATHselects/usr/bin/git, Apple's launcher that starts the Git of the active developer directory, the entry may run that Git directly instead, saving the launcher's start-up on each read. The prompt resolves it once per shell, in its own shell process, at its first Git status read: one/usr/bin/xcrun --find git(output bounded to 4 KiB) and one/usr/bin/codesign --verify --strict -R '=anchor apple and identifier "com.apple.git"'on the named file. Only status 0 from that check makes the file eligible; any other answer, a missing tool, or a path that is not an absolute regular executable keeps/usr/bin/git, and the refusal is remembered for the same selectors. The check uses the file's own signature and Compozsh requests no notarization or online revocation check. The file's zstat identity is recorded before and confirmed after the check, then compared again before every direct run; any difference runs/usr/bin/gitfor that call, and the next prompt verifies again. A changedPATHchoice, exportedDEVELOPER_DIR,TOOLCHAINSorSDKROOT, or developer directory (an xcode-select switch, or Xcode or the Command Line Tools installed or removed at a default place, found as under Missing developer tools below) resolves again;compozsh --refreshforgets the answer. A Git chosen elsewhere onPATH, a link to/usr/bin/gitincluded (it runs through the launcher), is never resolved, verified or replaced, transparentgandgitarguments always run thePATHchoice, and other entry callers only reuse a prompt's answer. The direct Git runs without theSDKROOT,MANPATH,CPATHandLIBRARY_PATHthe launcher adds. Git does not use them, and these reads keep hooks, content filters, the pager and signature verifiers off (Git review also turns off external diff and textconv programs). Like the launcher, which runs whatever its developer directory holds, this cannot stop a file swapped between the last identity check and the run. Inspect_compozsh_git_local_resolvein.zsh.addons/support/functions/.zsh.impure.compozsh_git_local, and check the file yourself withcodesign --verify --strict -R '=anchor apple and identifier "com.apple.git"' "$(xcrun --find git)"(expected: no output, status 0). - Missing developer tools. Without Xcode or the Command Line Tools,
running one of Apple's
/usr/binlaunchers, such asgit,xcrun, the compilers,swiftorpython3, makes macOS offer to install them. On macOS, three passive reads first look for a developer directory the way the launcher does, with file tests only (_compozsh_developer_dir): an exported, non-emptyDEVELOPER_DIR, else the first xcode-select link (/var/select/developer_dir,/var/db/xcode_select_link,/usr/share/xcode-select/xcode_dir_link), else Xcode, the Command Line Tools or Xcode beta at their default places, each counted only when it (or itsContents/Developer) holdsusr/lib/libxcrun.dylib. With none, whenPATHselects/usr/bin/git, directly or through a link, the prompt shows no Git context and runs neither its Git reads nor the launcher resolution above, and Search's default source is not Git; and when a C, C++, Objective-C, assembly, Swift or Python runtime probe would run/usr/bin/c++,clang++,g++,clang,cc,swiftorpython3(found onPATH, directly or through a link), the prompt shows that runtime asnot-installedand runs nothing. That answer is never remembered, so the next prompt after an install finds Git and probes those runtimes again. Git you start yourself (g,git, the Git workspaces, an explicitly chosen Git Search,g --discard-all) still runs/usr/bin/git, which may bring up the offer, and a Git or runtime elsewhere onPATHis unaffected (a shim there that starts one of these launchers, such as pyenv'ssystemPython, can still bring it up). On other systems/usr/bin/gitis an ordinary Git and these reads run it as usual. Inspect.zsh.addons/support/functions/.zsh.impure.compozsh_developer_dirand its callers in.zsh.impure.compozsh_git_localand.zsh.impure.prompt_runtime_version. Without running any launcher,ls -lf /var/select/developer_dir /var/db/xcode_select_link /usr/share/xcode-select/xcode_dir_link /Applications/Xcode.app/Contents/Developer/usr/lib/libxcrun.dylib /Library/Developer/CommandLineTools/usr/lib/libxcrun.dylib /Applications/Xcode-beta.app/Contents/Developer/usr/lib/libxcrun.dyliblists each of these paths that exists, in the order the probe checks them (each missing one is reported asNo such file or directoryfirst); an exportedDEVELOPER_DIRreplaces them all. The prompt shows Git and those runtimes when the first entry listed is a link (->) to a folder that holdsusr/lib/libxcrun.dylibitself or underContents/Developer, or, with no link listed, when anylibxcrun.dylibis listed; six missing paths mean it shows neither (the focused tests arewithout developer tools). - Repository selection. Interactive branch, review, worktree and explicit
discard workspaces, and explicit-root review providers, use the current
folder's repository, temporarily shadowing inherited Git directory, worktree,
common-directory, index, object-store, alternate-object and namespace
selectors; the caller's environment survives, and transparent
garguments and the prompt keep native Git-context behavior. - Revision comparisons acquire local refs only on chooser entry and resolve literal names/IDs only on submission or direct entry; filtering, scrolling, resizing and the guide discover nothing. Refresh keeps both commit IDs and any resolved common ancestor; missing objects and ambiguous ancestry fail visibly without fetching or switching method. The ref catalog holds no author identities or commit subjects.
- Working changes refresh is the only automatic local recheck, only while
its review screen is open: one bounded worker/provider pair at a time,
results through private pipes and bounded memory, and the same disabled
filters, hooks, lazy fetch, prompts and transport as manual refresh. Checks
start enabled; pause with Ctrl-A or
ZSH_GIT_REVIEW_AUTO_REFRESH=0set before peers load. Intervals adapt from a two-second floor to measured cost; a thirty-second worker deadline bounds capture and delivery even with input queued. The response FIFO is opened without following links and written nonblocking in chunks of at most 8 KiB; cleanup drains incomplete packets, terminates and reaps owned processes and removes the FIFO. No daemon, persistent cache, Git hook or fetch is created. - Filter overrides. One pure builder protects manual and automatic review
refresh, prompt status and guarded discard reads: filter discovery and its
inert override argv share a 4,096-entry bound, and a driver name containing
=is refused because Git's command-line configuration cannot unambiguously override it. Discard also requests inert smudge overrides. Filter names come from the shared bounded reader, which keeps completion and exit status apart from the payload; Review may show a marked partial prefix, while Worktree and filter discovery require complete captures. The byte bound is not a wall-time deadline. - Worktrees disable transport and hooks and refuse checkout filters (Git worktrees).
g --discard-allapplies these controls to initial HEAD validation (where a missing promised object likewise cannot start transport), preview, restore, cleanup and verification, and after confirmation revalidates repository, HEAD, operation (before and after: any merge, autostash, cherry-pick, revert, bisect, rebase or sequencer marker of any file type, the check worktree move and removal also use), filter-name and listed-path state. An existing index lock refuses before writes and is never removed. Native untracked cleanup runs before tracked restoration while reviewed ignore rules apply; ignored files newly exposed by restoring.gitignorestay on disk and yield a remaining-changes report with status 1. An empty committed tree supports untracked-only cleanup. If tracked restoration fails after cleanup, the diagnostic reports the partial outcome; completed deletion is not rolled back. Restore disables submodule recursion even undersubmodule.recurse=true, preserving child contents; a dirty child can still make verification report remaining changes (status 1) after parent changes are discarded.- Files discovery uses bounded filesystem reads, local Git metadata, or the
local Spotlight index. Its Git detection and capture share one
explicit-folder boundary with the same selector isolation and disabled
filesystem monitors, hooks, replacement objects, optional writes, lazy
fetches and transport, so repository-configured fsmonitor commands cannot
run during
ls-files. Inspect.zsh.addons/.zsh.findand the shared_compozsh_git_localentry.
The following independently controlled software can use the network; none is a permission for Compozsh to add a request, destination, or data:
- Cloning and updating the repository, and network-capable Git subcommands the user explicitly runs, use the configured Git transport. Git also documents that a partial clone may demand-fetch a missing object during an otherwise local command; that is installed Git behavior against the repository's configured promisor remote, not a Compozsh endpoint. Compozsh's own Git calls disable lazy fetching as listed above.
- Prompt runtime detection invokes a trusted-path installed runtime with a fixed
version argument from
/. Common auto-install and telemetry controls are disabled where supported, but the executable onPATHremains independently trusted software with its own behavior. Scala CLI's version capture explicitly passes--offlineto suppress its documented update check; it displays the default Scala version rather than the CLI launcher version. See Scala CLI version. An ambiguousscalalauncher is left unprobed and labeledlauncher-managed; its incompatible classic/modern interfaces cannot safely identify themselves through a speculative version invocation. - Explicit Xcode build, test, analyze, clean, run, LLDB, and Apple skill-export
actions invoke Apple's tools. Discovery disables automatic package resolution
and updates; a chosen build can execute project build phases. Physical-device
Run explicitly asks Apple's
devicectlto install and launch the app on the selected device over its configured connection, as detailed under Xcode capture, Run and logs. - Open and Reveal actions can launch Finder or another installed application. That application's later behavior is outside this repository.
- An explicit branch switch, including creating a local branch from a
remote-tracking one, runs the repository's configured checkout filters and
post-checkout hooks (see Git review and branches);
worktree operations disable hooks and refuse filters. Any explicit directory
change — entering a worktree, a Browse or Recents directory change,
mkcd— runs the user's ownchpwdhooks, as an ordinarycdwould. Each has its own effects and network behavior. Private add-ons and the machine-local initializer are likewise independently controlled code. - The repository's contributor skills in
.claude/skills/are Claude Code project skills. They run when a contributor invokes them by name or asks Claude Code for the task in their description, inside a forked subagent of the same Claude Code session that has no file-editing tools and is instructed to stay read-only (shell commands other than the listed pre-approvals still need the contributor's approval); the coding agent and its model provider apply their own network and data policies, and content the agent reads is sent to that provider as context.compozsh-platform-reviewasks the agent to consult current Apple documentation and official upstream Zsh and tool documentation, and pre-approves page reads only fromdeveloper.apple.com,support.apple.com,opensource.apple.comandzsh.sourceforge.io(other fetches and web searches still prompt); its local snapshot script contains no URL request and omits personal machine data.compozsh-release-draftasks the agent to read this project's public GitHub release metadata and tag resolution using only public, unauthenticated reads, never the user's credentials, an authenticated session orgh, and pre-approves page reads fromgithub.comandapi.github.com; those reads run without a per-request prompt from the contributor's machine. Its instructions prohibit sending repository contents, local paths, credentials, or private remote parameters in those requests; the agent, its provider, and GitHub retain their own network and data policies. Neither skill pre-approves shell commands beyond the platform snapshot script andsw_versversion queries. The shared.claude/settings.jsonpre-approves onlyzsh tests/run.zsh,zsh tests/verify.zshandzsh -fn -- <file>syntax checks (the--keeps later arguments from re-enabling execution). Pre-approving the runner means an agent can run the working tree's own test code without a shell prompt, relative to its current directory. The file denies Claude Code reads and edits of~/.zshrc,~/.zshenv,~/.zprofile,~/.zlogin,~/.zsh_history,~/.zsh_sessions/and~/.zsh.addons/; those rules also cover recognized shell file commands such ascat,teeandsedand input or output redirections, but not other programs or scripts, and a customZDOTDIRorHISTFILElives elsewhere. It registers a local hook that runszsh -fnon an edited Zsh file and reports the first parse error. None of it adds network access.
The optional docs/ website is static and self-contained. Its Content Security
Policy uses connect-src 'none'; JavaScript has no fetch, beacon, WebSocket, or
remote script path. Links and canonical metadata point to the official GitHub
project, but a network navigation occurs only when the browser loads the hosted
page or the user follows a link. GitHub or another chosen host may retain normal
web-server access logs; that hosting boundary is not controlled by the static
files in this repository.
The interactive examples run entirely on fixed synthetic data in page memory. They execute no shell code, read no user configuration or visitor files, and use no browser storage, clipboard access, provider lookup or network request; reloading or closing the page discards their state, and re-entering an example resets its controls. The composition model's three fixed peer labels and its loading-order and repeated-load controls change only that illustration. The Context tab switches among fixed synthetic prompt moments and never inspects the visitor's terminal, clock, command line, filesystem, Git repository, environment, or project metadata. Help → Compose accepts at most 120 characters per field and quotes them into a displayed sample; Replace draft changes only an editable page control, never submitting a command, and reset discards the values. Do not enter secrets in the demo. The Change atlas groups six fixed synthetic entries. Picker and Git-review filters accept at most 120 characters per field and derive results from fixed sample paths, labels and context; the Git example keeps view choice, folds, selection and reading positions in page memory and summarizes three synthetic entries. Keyboard guides keep only their caller's browser focus. These illustrative limits are separate from the native tool contracts above.
Run these commands from the clone. They use the required Git and stock shell tools; they do not execute Compozsh.
First establish exactly what is being reviewed:
git -c core.fsmonitor=false status --short
git rev-parse HEAD
git remote get-url --all origin
git ls-files .zshrc install.zsh templates .zsh.addons docs tests SECURITY.mdAn empty status means tracked files match the checked-out commit. Record the
commit ID. The expected project remote is
https://github.com/bitbemol/compozsh.git or
git@github.com:bitbemol/compozsh.git; a remote URL should not contain a token
or password, and its output should not be pasted into a report without review.
Review any local modification or untracked .zsh.<name> file before starting
Zsh: the bootstrap loads an untracked .zsh.<name> file in the clone, and
compiles it into the compiled mirror, as a shipped
peer.
Inspect the only automatic source boundary:
cat .zshrc
git grep -nE '(^|[[:space:]])(source|eval)[[:space:]]' \
-- .zshrc install.zsh '.zsh.addons/**' templatesThe tracked bootstrap sources the optional machine-local initializer and every
matching peer beneath the repository and user add-on directories, excluding the
reserved support/cache data directory, the support/compiled
mirror, .zwc files and hidden folders. A shared peer may run from that
compiled mirror only under its checks
(zsh tests/run.zsh 'compiled peers'). Before any peer loads, the bootstrap may
run the shipped .zsh.addons/support/compile.zsh in Apple's /bin/zsh -f from
/, which
compiles (parses, never runs) shipped peers into that mirror. Discovery runs
under fixed Zsh options, so glob options set by the initializer or a peer
cannot widen it
(zsh tests/run.zsh 'peer discovery ignores glob options'). An explicit
ZSH_LOCAL_INIT must be an absolute path: the bootstrap ignores a relative
initializer path with a diagnostic and never resolves it against the current
directory (zsh tests/run.zsh 'relative ZSH_LOCAL_INIT'). The tracked
starter is inert, but an existing private initializer or peer is user-owned
code and must be audited separately. Within that boundary, peers autoload
stock Zsh functions from fpath (such as compinit and
add-zle-hook-widget), and .zsh.editor's compinit also sources Zsh's own
completion dump from the configuration base (or,
while it is provably unchanged, its compiled copy in that mirror, after which
the same compile.zsh may compile a new or changed dump) and autoloads
completion functions. Compozsh adds no fpath entry, and neither
path comes from the current directory while fpath holds only absolute
directories; an inherited relative FPATH entry would make autoload search
the current directory.
Search the executable shell surface for common network clients:
git grep -nE -e '(^|[;&|[:space:]])(command[[:space:]]+)?(/usr/bin/)?(curl|wget|ssh|scp|sftp|nc|netcat|socat|telnet|rsync)([[:space:]]|$)' \
--and --not -e '^[[:space:]]*#' \
-- .zshrc install.zsh '.zsh.addons/**' templatesFull-line comments are excluded, matching the native regression check; the prompt classifier mentions network clients as inert draft text. The command should print no matches and return status 1. This is a useful regression check, not proof by keyword absence; also read new command execution, redirection, dynamic function dispatch, and source paths in the diff.
Audit the worktree action boundary before exercising it:
git show HEAD:.zsh.addons/.zsh.git-worktree
git show HEAD:.zsh.addons/.zsh.navigation
zsh tests/run.zsh 'worktree 'Read the fixed Git policy in _git_worktree_git, complete-capture and
streamed-listing checks, creation/removal validation and post-screen dispatch.
The tests cover aliases/fallbacks, exact targets, refusals, move preservation
of local files, branch preservation and native keyboard/screen behavior.
Audit the living prompt's local presentation boundary:
git show HEAD:.zsh.addons/.zsh.prompt
git show HEAD:.zsh.addons/.zsh.editor
zsh tests/run.zsh 'living prompt'Read the ordinary precmd fact capture and its repeat after an in-widget
directory change, the bounded Context trigger fingerprint, the private
compact/lens/transcript modes, the Context/Interaction renderers, and the
editor peer's line-init/pre-redraw/line-finish transitions, post-cleanup
directory-change recapture and compozsh-context-lens Option-I widget. The
tests check that READY, operation-specific, advisory, assignment-value,
autosuggestion, pin, transcript and resize behavior preserve the exact ZLE
buffer, replace providers and executables with sentinels to keep Interaction
edits inert, and instrument provider calls in a resize PTY. They also check
that Change directory and Enter linked directory recapture the new folder's
facts and title while keeping the draft and a manual pin, without a precmd
or history entry.
Audit alias previews and manual capture without reading private shell settings:
zsh tests/run.zsh 'alias coverage'
zsh tests/run.zsh 'tool descriptions'
zsh tests/run.zsh 'manual coverage'
zsh tests/run.zsh 'manual summaries'Read _prompt_alias_expansions and the .zsh.manual capture/reader/formatter
helpers alongside these tests. Fixtures cover current, quoted and suppressed
aliases, nonexecution during native ZLE painting, MANPATH ordering,
links/compression/forwarding, literal configuration, unavailable NAME
summaries, and inert formatter command requests and caller-folder includes.
Audit Simulator and device monitoring without launching Xcode or a real app:
git show HEAD:.zsh.addons/.zsh.xcode
zsh tests/run.zsh 'Xcode run'
zsh tests/run.zsh 'Xcode Logger'
zsh tests/run.zsh 'Xcode logs'
zsh tests/run.zsh 'Xcode monitor'
zsh tests/run.zsh 'Xcode device monitor'
zsh tests/run.zsh 'log reader'
zsh tests/run.zsh 'log copy'
zsh tests/run.zsh 'editor reader'Read _xcode_run_session, which declares both modes' Run state once and owns
their pipe and cleanup, with its mode prepare, start and stop helpers and
_xcode_run_attach; then _xcode_run_identity, _xcode_run_idle, their scoped
log-observer and _xcode_logs_* helpers, the Copy-and-reopen loop
_xcode_run_screen both modes share, the shared picker's screen cleanup, and
for device consoles _xcode_device_poll.
Check literal launch arguments, owned-job termination, failure preservation,
temporary FIFO permissions, post-screen cleanup and clipboard dispatch, copy
payload bounds, matching and exclusion over captured text, following and
paused/Options capture boundaries, full-reader returns that keep the app
running, the exact device and installed-executable predicate, child-only
LOGRC and unbuffered launch settings, the readiness bound, fair draining and
line bounds, source failure disclosure, and observer stop/reap before LLDB and
on cleanup. The tests also cover malformed launch responses, frozen reading,
native PTY resize/guide/cancellation, identity replacement and failure status.
For each git show HEAD:... block, run against the exact checkout being
audited; before commit, read the corresponding working files instead.
Audit website connection primitives and all hard-coded destinations:
git grep -nE 'fetch\(|XMLHttpRequest|WebSocket|sendBeacon|src="https?://|@import' -- docs
git grep -nE 'https?://|wss?://|ftp://' \
-- .zshrc install.zsh '.zsh.addons/**' templates docs .claudeThe first command should print no matches. The second is intentionally broad:
review every URL. Expected tracked URLs are documentation comments, official
project links and website metadata. The optional platform-audit inventory also
runs /usr/bin/curl --version; it records the installed binary's version and
does not give curl a URL.
Review local storage and sensitive effect boundaries:
git grep -nE 'HISTFILE|compinit|mktemp|\.zsh-backups|pbcopy|sudo|/dev/(r)?disk|diskutil|createinstallmedia' \
-- .zshrc install.zsh '.zsh.addons/**' templates README.mdCheck that data never passes through Zsh's temporary-file redirections:
git grep -n -e '<<' -- .zshrc install.zsh '.zsh.addons/**' templates
git grep -nE '(^|[[:space:];|&(])=\(' -- .zshrc install.zsh '.zsh.addons/**' templates
zsh -fc 'print -r -- <(:) >(:)'
zsh -fc 'grep -n "=(<<<" /dev/null $^fpath/edit-command-line(N)'The first command prints four lines and no redirection: quoted operator
patterns in the prompt's command classifier (two in .zsh.prompt, one in
.zsh.pure.compozsh_is_redirection) and the %<< prompt truncation escape in
.zsh.pure.zle_picker_abbreviate. The second prints nothing and returns status
- The third prints two
/dev/fd/Npaths, showing that process substitution uses pipes. The fourth shows the one line of Zsh's stockedit-command-linethat writes the Ctrl-X Ctrl-E draft to a temporary file, as disclosed in the inventory.
Finally run the isolated regression suite:
zsh tests/run.zshTests improve confidence in documented behavior but do not replace source review. A new commit is new code and should be compared before it becomes the active symlinked configuration.
Run one boundary's checks with zsh tests/run.zsh '<filter>' from the
repository root; a filter selects every test whose description contains it,
ignoring case. Expected result: all selected tests pass. The tests use isolated
homes, synthetic fixtures, disposable repositories and command stand-ins; they
read no private configuration or real cache contents and contact no remote
server.
| Boundary | Filters |
|---|---|
| Appearance, contrast and plain output | appearance |
| Shared screens and native ZLE cleanup | UI, picker screen |
| Living prompt buffer preservation, inert edits and the in-widget directory-change recapture | living prompt |
| Terminal title without assignment values | running-command title |
| Display control sanitization, including UTF-8-encoded C1 in every locale, and literal printing of sanitized text | sanitiz, encoded C1, name literally |
| READY toolchains without extra reads or probes | runtime prompt |
| Help workspace capture, fallback and cleanup | help workspace |
| Tool descriptions and alias previews | tool descriptions, alias coverage |
| Draft inspector | task experience |
| Command composer | command composer |
| Runtime metadata bounds and installed-runtime probes | runtime versions, bounded direct metadata, prompt probes installed runtimes |
| Manual capture, formatter and transport | manual coverage, manual summaries, manual async |
| Public manual cache and installer exclusion | manual cache, cache data, installer excludes generated |
Native completion dump call, path, contents, mode and --clean survival; its compiled copy's freshness, fallbacks and rewrite |
native completion dump, clean installer touches only, compiled completion dump |
Compiled peer mirror: freshness, ownership, parse context, /bin/zsh only, startup recompile, locking, size bound, Git ignore and installer lifecycle |
compiled peers |
| Installer rollback after failures and signals, copy staging name and cleanup | installer |
| Matching and exclusion | exclusion, matching component |
| Captured-data parsing without temporary files | here-string, filter override builder, worktree removal |
| Captured, setting and inherited text kept out of arithmetic | never splices text into arithmetic, stay text, overflowing, hostile, inherited, stat numbers, dd parser, validates width text |
| Shared effects and data | shared effects, shared data |
| Picker digits and slot acceptance | numeric, detail panels preserve native |
| Path + Tab directory arguments | directory argument |
| Bracketed paste | picker nonpaste CSI, picker interrupted paste |
| Job lifecycle | job lifecycle |
| Git file review and Change atlas | Git tree, Git atlas, Git review native g |
| Branch catalog and remote fallback | absent from the reflog, branch remote |
| Git filter, selector, Files, promisor and signature-verifier controls | shared Git, Git selectors, Files Git, promisor fetch, signature verifier |
Direct Apple Git behind /usr/bin/git: signature, identity, selectors, PATH choice and refresh |
git shim |
Without Apple's developer tools, the prompt's Git context and runtime probes and Search's default source run no /usr/bin Git, xcrun, compiler, swift or python3 launcher |
without developer tools |
Without a JDK, the prompt's Java probe runs no /usr/bin/java stub, and its file tests count only runtimes the stub could start |
Java stub |
| Git comparison bounds | Git comparison |
| Discard with a dirty submodule | g --discard-all preserves dirty submodule |
| Worktrees | worktree |
| Xcode discovery | Xcode discovery |
| Xcode Run, device console and logs | Xcode run, Xcode monitor, Xcode device monitor, log reader, log copy, editor reader |
| Task routing and external-device modes | task families |
| USB progress, phase and disk-size number bounds | USB numeric bounds |
| External media writes, read-back and temporary files | USB raw, USB verification, USB temporary, ISO inspection, external-media temporary |
| Source disk excluded from targets; media on a virtual disk refused | USB source exclusion |
| External device captures released when the task returns | USB invocation state |
Apple signature before createinstallmedia runs as root |
USB Apple signature boundary, USB macOS |
| Touch ID policy in unprivileged fixtures | sudo Touch ID |
These checks do not establish: an operating-system sandbox or OS protection
boundary; defense of a compromised account; atomicity against concurrent
external writers, ref or configuration changes, or filesystem mutation; Git
server freshness; visual behavior in Terminal.app on every Mac, arbitrary
terminal profiles' contrast, or how a custom terminal stores scrollback;
mounted-filesystem availability or latency; exhaustive shell grammar, manual
formats or CAUTION coverage; arithmetic in code held in single-quoted
strings, such as the fixed sudo routines, which the arithmetic scan does not
lex; version-manager shim behavior; macOS application behavior or prevention of
clipboard synchronization; for external media, real device writes, sudo,
diskutil, hdiutil or createinstallmedia behavior; or, for Xcode,
Simulator FIFO support, native log privacy behavior, complete log delivery,
signing permission or LLDB attachment on the host.
git pull changes a symlink installation immediately. To review first, fetch
the proposed commit without moving the working tree, compare it, and only then
fast-forward:
git fetch origin
git --no-pager log --oneline --decorate HEAD..origin/main
git --no-pager diff --no-ext-diff --no-textconv --text HEAD..origin/main -- \
.zshrc install.zsh .zsh.addons templates docs tests SECURITY.md README.md
git merge --ff-only origin/main
exec zshFetching necessarily contacts the configured remote; it does not activate the
fetched shell files. Replace origin/main with the exact remote ref you intend
to trust. Compare the recorded old and new commit IDs, inspect renamed and new
files, and rerun the audit and tests. Copy installations do not change until the
reviewed installer is run again. This fetch is a deliberate command run by the
user for an update review; Compozsh never performs it automatically.
Security fixes target the current main branch and the next release. This
repository does not currently maintain separate long-lived release branches;
older tags should not be assumed to receive backports unless a security
advisory says otherwise. Reports for any version are welcome—include the exact
commit ID so the affected behavior can be reproduced.
Reporting is optional, deliberate communication initiated by the user and is not performed by Compozsh. GitHub receives whatever the reporter chooses to submit, so review and redact the report before sending it.
Do not place a live password, token, private key, personal path, private repository content, or exploitable sensitive detail in a public issue. Use GitHub private vulnerability reporting when it is available. If that route is unavailable, open a minimal issue asking for a private reporting channel without including the sensitive details.
Include the affected commit ID, macOS and Zsh versions, the smallest synthetic reproduction, the observed data or privilege boundary, and whether credentials may have been exposed. Revoke or rotate exposed credentials immediately; a source-code fix cannot make a disclosed secret private again.