CodeVetter · Repo Unpack · Contributor handoff
ohmyzsh/ohmyzsh
This is a pure-shell repo: no build step, no package manifest, no unit-test harness wired to CI. The safe edit boundary is 'add or modify files, don't reorder init'. Read oh-my-zsh.sh first (it defines load order and the _omz_source override contract), then the specific lib/ or plugins/ file. Validate changes with `zsh -n` on every touched file — that is literally all CI runs. Mind zsh-only syntax (emulate modes, zstyle, zparseopts, associative arrays), POSIX-sh vs zsh script split (install.sh is sh; upgrade.sh/tools are zsh), and that many variables/hooks are user-facing API you cannot rename.
- Read order for any task: oh-my-zsh.sh (init order, _omz_source, compinit handling) -> templates/zshrc.zsh-template (the user-facing config contract) -> the target lib/*.zsh or plugins/<n>/<n>.plugin.zsh -> CONTRIBUTING.md commit conventions.
- Never reorder the load sequence in oh-my-zsh.sh: plugin fpath entries must precede compinit, lib files precede plugins, $ZSH_CUSTOM/*.zsh loads last, and the theme loads after everything so it can use lib-defined functions.
- tools/install.sh is POSIX sh (#!/bin/sh, run via curl|sh) while tools/upgrade.sh, tools/check_for_upgrade.sh, and tools/changelog.sh are zsh scripts — do not use zsh-isms in install.sh or assume sh semantics in the tools.
- The only CI gate is `zsh -n` over oh-my-zsh.sh, lib/*.zsh, plugins/*/*.plugin.zsh, plugins/*/_*, and themes/*.zsh-theme — replicate it locally before submitting; there is no formatter config for shell (only .prettierrc for docs at 110 cols).
- Public API you must not break: plugin/theme/env-var names (ZSH_THEME, plugins, ZSH_CUSTOM, ZSH_CACHE_DIR, DISABLE_*, HIST_STAMPS), zstyle contexts ':omz:...', prompt functions (git_prompt_info, parse_git_dirty, *_prompt_info stubs), and aliases — all are consumed by user dotfiles and external themes/plugins.
- When writing shell that runs at startup, avoid forks and subshells; the codebase caches probes in $ZSH_CACHE_DIR (grep-alias, localhostname, appearance-probes) precisely to avoid per-start process spawns — follow that pattern for new probes.
- Quote % in anything that reaches the prompt: git.zsh escapes % to %% in branch names and upstreams, vcs_info.zsh exists because unquoted hook_com fields enabled command substitution (CVE-2021-45444), and _omz::log sets nopromptsubst for the same reason.
- Use `command`/`builtin` prefixes for external calls in lib code (diagnostics.zsh documents why: users redefine echo/which), and route git prompt calls through __git_prompt_git so GIT_OPTIONAL_LOCKS=0 is set.
- ZDOTDIR is a first-class citizen: all .zshrc-mutating code resolves ${ZDOTDIR:-$HOME}, and the installer picks $ZDOTDIR/ohmyzsh as $ZSH when ZDOTDIR is set and != HOME — replicate this resolution rather than hardcoding ~/.zshrc.
- Trap: editing the plugin-enable/disable awk logic means updating two copies — lib/cli.zsh and the duplicated copy inside lib/tests/cli.test.zsh.
- The devcontainer (.devcontainer/devcontainer.json) symlinks the workspace to ~/.oh-my-zsh and installs templates/minimal.zshrc as ~/.zshrc — the fastest way to smoke-test a change is a new container or `zsh -f` with a minimal zshrc sourcing the repo.
- New aliases face an explicit bar in CONTRIBUTING.md (used by many, common tasks, prefer generic over specific, don't shadow existing commands), and new themes are currently not accepted at all — check these policies before generating either.
- Known fragile spots to test manually: compfix/compinit interaction (ZSH_DISABLE_COMPFIX), async prompt registration ordering (_defer_async_git_register must run before _omz_async_request), and the update.lock EXIT trap's $ret propagation for SIGINT.
- Commit messages must follow Conventional Commits with scope = plugin/theme name (e.g. feat(git):, fix(agnoster):) because tools/changelog.sh parses subjects into the user-facing changelog; '!' marks breaking changes.