A plugin that turns the workflow rules you already wrote in CLAUDE.md into things
that actually fire at the moment they are needed — a merge that stops while a blocking
review finding is still open, a "done" that will not go out without evidence behind it,
a worktree workflow that is not skipped because the change looked small. Skills you
invoke by name, and hooks that fire whether or not anyone remembers them. One runtime,
packaged for Claude Code, Codex, and Cursor.
Note: Skills may be added, removed, or restructured at any time without prior notice. This is a personal toolbox — not a stable API.
Praxis (πρᾶξις) is theory carried into action — where a stated principle stops being a
statement and becomes something done. That is this repository's whole design, written in
ETHOS.md as spec defines, hook enforces: every hook here is the
structural enforcement of a rule that already existed as prose in a CLAUDE.md or a
memory entry, and exists precisely because the prose had already failed at the moment it
was needed. The skills sit on the same axis — strike, debt, spec-drift, and
merge-briefing all make an already-decided rule reachable at execution time rather
than deciding anything new.
The word carries none of that domain on its own, which is what the paragraph above is
for. It is also a crowded name: the Praxis API framework (Ruby), PraxisEMR, and several
unrelated npm and PyPI packages share it. None of them share a namespace with this
repository — the surfaces that resolve here are devseunggwan/praxis, the praxis:
skill prefix behind /praxis:retrospect, and the PRAXIS_* environment variables.
/plugin marketplace add https://github.com/devseunggwan/praxis
/plugin install praxisClaude Code reads .claude-plugin/plugin.json and .claude-plugin/marketplace.json
directly from the repo root.
# Register this repo as a marketplace (its root is .agents/plugins/marketplace.json)
codex marketplace add https://github.com/devseunggwan/praxis
codex plugin install praxisCodex reads .agents/plugins/marketplace.json as the marketplace root and
plugins/praxis/.codex-plugin/plugin.json as the plugin root. The skills/,
hooks/, and scripts/ directories inside plugins/praxis/ are symlinks
into the repo-root runtime — there is no source duplication.
When the plugin surface isn't available, Claude Code still loads personal
skills from ~/.claude/skills/<skill-name>/SKILL.md (project skills from
.claude/skills/). Clone the repo and link the skill directories you want:
git clone https://github.com/devseunggwan/praxis.git ~/projects/praxis
mkdir -p ~/.claude/skills
ln -s ~/projects/praxis/skills/<skill-name> ~/.claude/skills/<skill-name>Skills installed this way are invoked as /<skill-name> rather than
/praxis:<skill-name>, and the hooks are not installed — this path ships
skills only. Skills that call a bundled helper through CLAUDE_PLUGIN_ROOT
(strike, spec-drift, the cmux-* skills) need that variable exported to
the clone path, e.g. export CLAUDE_PLUGIN_ROOT=~/projects/praxis.
Three reading paths, each 3–4 steps. Time estimates are for a first read.
Fix or add a hook (~25 min)
ETHOS.md→ Hook Ethos — why a hook exists at all and what it may never do (5 min)DESIGN.md→ Adding a new hook — the shared contracts and the per-hook checklist (10 min)- The hook's own
hooks/<role>/<name>/spec.md, found viadocs/hook/INDEX.md— what it blocks, passes, and how it fails open (5 min) CONTRIBUTING.md→ Adding or modifying a hook — registration, tests, and the runtime canary (5 min)
Change a skill (~15 min)
RUNTIME_CONSTRAINTS.md— the fixed Claude Code limits a skill must fit inside (5 min)skills/SKILL.md.tmpl— the frontmatter and section skeleton to copy (2 min)CONTRIBUTING.md→ Adding or modifying a skill — the live-runtime verification gate (8 min)
Change packaging or manifests (~10 min)
ARCHITECTURE.md→ Multi-Platform Packaging — canonical source, generated outputs, add-a-platform flow (6 min)CONTRIBUTING.md→ Packaging — which files are generated and how to regenerate them (4 min)
Eighteen skills, grouped as Discovery, Development, Discipline, and Session Management.
The full table — trigger keywords, when to use each, example invocation — lives in
docs/skills.md.
If you are new, /praxis:using-praxis maps situations onto the skill that handles each —
sessions lost to a crash, a broken rule you want on record, a review whose comments have
piled up — which is a shorter read than the full table. The three worth knowing by name
on day one:
| Skill | What it is for |
|---|---|
/praxis:using-praxis |
Finding the right skill when you don't know what exists yet |
/praxis:retrospect |
After a session that went badly — find the friction's root cause and act on it |
/praxis:merge-briefing |
Before merging — probe all three finding surfaces, then brief and ask |
Praxis also ships bypass-review, a shell wrapper with no SKILL.md. It is not
invocable as /praxis:*; it reads the review bypass-telemetry event logs. See
CONTRIBUTING.md → Local development for every
shipped CLI wrapper.
Hooks are the larger half of praxis: 98 hooks, registered at 113 points across
PreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch, Stop,
SubagentStop, UserPromptSubmit, and SessionStart. They run
without being invoked, so this section is the one to read before installing — it is what
changes about your session.
They divide into four roles. Two of them block by default, and a third can be promoted into blocking:
| Role | Count | What it does |
|---|---|---|
preflight-gate |
37 | Inspects a tool call before it runs and can deny it |
completion-verify |
13 | Fires at Stop — can block a response that claims completion without evidence |
advisory-nudge |
43 | Prints a warning to stderr and lets the call through — 18 read a PRAXIS_*_STRICT variable that makes them stop the call instead |
postuse-correction |
5 | Reacts after a tool call — telemetry, follow-up signals |
Concretely, what a gate stops looks like this — gh issue create without a duplicate
search first (block-gh-issue-create-without-dup-search), an edit to a file while you
are standing on a protected branch (pre-edit-protected-branch-guard), a merge run from
the wrong worktree (gh-merge-worktree-precondition), gh search --state all which that
subcommand does not accept (block-gh-state-all), a foreground sleep-and-poll loop
(foreground-poll-loop-guard), a commit whose title breaks the repo's format
(commit-title-format-check).
Two properties are load-bearing. Hooks fail open: a missing jq, a malformed stdin
payload, an unreadable transcript — all exit 0, so a broken hook degrades to no hook
rather than to a broken session. And most blocks arrive with their own way out: the
shared deny-message helper (hooks/_lib/block_message.py) prints the hook's bypass
variable in the message, so you rarely have to go looking for it.
The complete list, with each hook's events, hosts, strict/bypass knobs, and the external
commands it may run, is the generated
Hook Operating Matrix. Per-hook specs live at
hooks/<role>/<name>/spec.md, indexed by docs/hook/INDEX.md,
and DESIGN.md → Hook Design Contracts covers the
contracts every hook follows.
A hook that blocks something you meant to do is not a wall. There are three levers.
One gate. 60 of the 98 hooks declare an opt-out or tuning variable. Which variable
belongs to which hook, and what setting it actually does to that hook, is the table in
docs/bypass-vars.md; the generated
Hook Operating Matrix carries the same mapping with each
hook's default alongside it. Read the row before setting the variable — the hooks differ
from each other, which is why this section points at the table instead of summarizing it.
Set it where Claude Code can see it — its own environment, before the session starts:
export PRAXIS_HOOK_BYPASS_SKILL_GATE=1 # <one-line reason>An assignment written in front of the command (VAR=1 git …) does not work. Hooks
read os.environ of their own process, which never sees a variable scoped to the tool
call, so the gate blocks exactly as before. Use the shell export above, or the env
block in your settings.json.
All of it. On a plugin install, claude plugin disable praxis (or /plugin in the
session) switches the whole plugin off — skills and hooks together, since both are
declared in one manifest and disable has no hook-only option. To keep the skills and
stop a gate, use the opt-out above instead. Only a manual install registers praxis hooks
in settings.json as separate entries; there, dropping them leaves the skills working.
Most skills delegate to external agents or session managers. Install the dependencies that match your usage tier.
| Dependency | Required for | Install |
|---|---|---|
| gh CLI | Standalone (recover-sessions), strike skills, PR/issue ops |
brew install gh |
| jq | Strike skills (session-scoped counter parsing) | brew install jq |
| oh-my-claudecode | Agent delegation (tracer, analyst, critic, code-reviewer) | omc install |
| cmux | Session management skills (cmux-*) | Mac app installer |
| codex-cli, gemini-cli | Multi-provider routing in cmux-delegate |
per upstream docs |
Hooks fail open, so a missing component never breaks a session — the hooks that
key on it simply never fire, and nothing says so. hooks/manifest.json declares
those components per hook in its requires field (#1158); this table is the
reader's view of that field, and scripts/check-plugin-manifests.py Rule 27
checks the two against each other in both directions (#1332).
| Component | Hooks inert without it | Install |
|---|---|---|
cmux |
model-routing-advisory |
Mac app installer (the Full tier below) |
codex-plugin |
codex-review-route |
/plugin marketplace add openai/codex-plugin-cc, then /plugin install codex@openai-codex |
hookable-memory-store |
memory-hint |
a memory directory whose entries carry hookable: frontmatter, located per hooks/_lib/_memory_dir.py (PRAXIS_MEMORY_DIR overrides) |
zsh |
block-unmatched-glob |
brew install zsh, or the distro package |
builtin-task-postuse is the one hook whose premise is another plugin rather
than a component: it corrects an oh-my-claudecode pre-tool-enforcer false
positive and is registered for the Claude host only (hosts), so it carries no
requires row. None of these components is declared as a plugin.json
dependencies entry — the harness has no optional-dependency concept, so a
declaration would turn every tier below into a hard requirement; see
ARCHITECTURE.md → Why plugin.json declares no dependencies.
| Tier | What works | What you need |
|---|---|---|
| Standalone | recover-sessions, strike / strikes / reset-strikes, debt | gh CLI, jq; recover-sessions also needs tmux; debt needs only git |
| Enhanced | + retrospect, codex-review-wrap | + oh-my-claudecode |
| Full | + all cmux-* skills | + cmux |
| Multi-provider | + codex/gemini routing in cmux-delegate | + codex-cli, gemini-cli |
Skills in higher tiers fall back to manual/built-in alternatives when their dependencies are missing, but with reduced functionality.
Skills that dispatch external CLI workers (cmux-delegate) can route tasks
to multiple AI providers via a unified --model flag using
<provider>:<model> notation (e.g. claude:opus, codex:o3,
gemini:flash). Bare names (opus, sonnet, haiku) always resolve to
Claude — full backward compatibility. When only claude is installed,
the system behaves exactly as before — no errors, no degradation.
See ARCHITECTURE.md → Provider Routing for the full task-type / complexity routing matrix and fallback policy.
Platform manifests are generated, not hand-edited. The canonical source is
manifests/plugin.base.json (common metadata) plus one file per platform
under manifests/platforms/.
# Regenerate every platform manifest + adapter shell symlinks
./scripts/build-plugin-manifests.py
# Verify committed manifests match the canonical source (CI / pre-merge)
./scripts/check-plugin-manifests.pyGenerated artifacts are committed:
.claude-plugin/plugin.json.claude-plugin/marketplace.json.agents/plugins/marketplace.jsonplugins/praxis/.codex-plugin/plugin.jsonplugins/praxis/{skills,hooks,scripts}(symlinks into repo root)
To add a new platform, add a manifests/platforms/<name>.json file listing
its outputs, add its host_id to the hosts enum in
hooks/manifest.schema.json (a test asserts the enum and the platform set
are equal), and run the build script — no changes to skills, hooks, or
existing platforms required.
When you work from a clone rather than the plugin cache, the CLI wrappers
shipped by skills (cmux-recover-sessions, claude-recover,
cmux-save-sessions, …) are installed as ~/.local/bin symlinks into
whichever clone ran scripts/install.sh — so a patch reaches the version that
runs at the shell only if it lands in that clone. One clone per machine keeps
the links honest; verify-symlinks.sh tells you when they are not.
# Install / refresh CLI symlinks (idempotent)
./scripts/install.sh
# Verify symlinks point at this clone (CI / SessionStart hook)
./scripts/verify-symlinks.shSee CONTRIBUTING.md → Local development for the full list of shipped CLI wrappers and drift-recovery rationale.
- SECURITY.md — vulnerability reporting and supported versions
- PRIVACY.md — what praxis reads, executes, and never transmits
MIT License