diff --git a/README.md b/README.md
index 5a5666b..3874961 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,9 @@ b install "git@github.com:org/private-repo:bin/app"
# Install and add to b.yaml
b install --add jq@1.7
+# Install with a post-install hook (saved to b.yaml with --add)
+b install --add github.com/arg-sh/argsh --on-post 'argsh builtin ${B_EVENT}'
+
# Install and pin version in b.yaml
b install --fix jq@1.7
@@ -136,6 +139,9 @@ binaries:
version: v1.0
oci://docker:/usr/local/bin/docker:
version: cli
+ # Post-install hook — runs after install/update when the binary changed
+ github.com/arg-sh/argsh:
+ onPost: argsh builtin ${B_EVENT}
envs:
# Sync files from upstream git repos
diff --git a/docs/b/subcommands/install.mdx b/docs/b/subcommands/install.mdx
index f4e2e52..3cf71fa 100644
--- a/docs/b/subcommands/install.mdx
+++ b/docs/b/subcommands/install.mdx
@@ -163,6 +163,43 @@ The syntax is: `[@][:/]`
The leading `/` on the path disambiguates it from an `image:tag` pasted from docker documentation. For private registries, `oci://` reads credentials from `~/.docker/config.json` (same as `docker login`); see the [authentication](/authentication) page.
+### Post-install hooks
+
+Run a shell command after a binary is installed or updated. The hook only fires
+when the on-disk binary actually changed — not on no-op skips or `--dry-run`.
+
+```bash
+# Add a binary with a post-install hook
+b install --add github.com/arg-sh/argsh --on-post 'argsh builtin ${B_EVENT}'
+
+# The hook is saved to b.yaml so it runs on future updates too
+```
+
+The hook receives these environment variables:
+
+| Variable | Description | Example |
+|---|---|---|
+| `B_EVENT` | `install` or `update` | `install` |
+| `B_NAME` | Binary name | `argsh` |
+| `B_VERSION` | Version being installed | `v0.6.6` |
+| `B_FILE` | Absolute path to the binary | `/project/.bin/argsh` |
+
+In `b.yaml`:
+
+```yaml
+binaries:
+ github.com/arg-sh/argsh:
+ onPost: argsh builtin ${B_EVENT}
+ kubectl:
+ onPost: kubectl completion bash > .completions/kubectl.bash
+```
+
+Hooks are POSIX shell commands (run via `sh -c`), executed in the **project root
+directory** — so relative paths like `.completions/...` resolve from there. Non-zero
+exit produces a warning but does not fail the install. Output goes to stderr so it
+doesn't interfere with progress bars. Hooks are also skipped during `b update --dry-run`
+and `--plan-json`.
+
## Flags
| Flag | Description |
@@ -170,6 +207,7 @@ The leading `/` on the path disambiguates it from an `image:tag` pasted from doc
| `--add` | Add binary/env to b.yaml during install |
| `--alias` | Install binary under a different name |
| `--fix` | Pin the specified version in b.yaml |
+| `--on-post` | Shell command to run after install/update (saved with `--add`) |
| `-h`, `--help` | help for install |
## Global Flags
diff --git a/docs/b/subcommands/update.mdx b/docs/b/subcommands/update.mdx
index 855ba68..6c6a8a0 100644
--- a/docs/b/subcommands/update.mdx
+++ b/docs/b/subcommands/update.mdx
@@ -106,6 +106,22 @@ Only update envs tagged with a specific group:
b update --group=dev
```
+### Post-update hooks
+
+Binaries configured with an `onPost` hook in `b.yaml` will run that hook after
+a successful update — the same way it runs after `b install`. The hook only
+fires when the on-disk binary actually changed (not on digest-match skips or
+`--dry-run`).
+
+```yaml
+binaries:
+ github.com/arg-sh/argsh:
+ onPost: argsh builtin ${B_EVENT} # B_EVENT=update
+```
+
+See [b install — Post-install hooks](/b/subcommands/install#post-install-hooks) for
+the full list of environment variables and examples.
+
## Flags
| Flag | Description |
diff --git a/docs/getting-started.mdx b/docs/getting-started.mdx
index d3cdd81..460af8e 100644
--- a/docs/getting-started.mdx
+++ b/docs/getting-started.mdx
@@ -182,6 +182,9 @@ binaries:
# custom file paths
custom-kubectl:
file: ../bin/kubectl # relative to config file
+ # post-install hook
+ github.com/arg-sh/argsh:
+ onPost: argsh builtin ${B_EVENT}
envs:
# Sync files from upstream git repos
diff --git a/docs/glossary.mdx b/docs/glossary.mdx
index f7cea83..4c9b4d6 100644
--- a/docs/glossary.mdx
+++ b/docs/glossary.mdx
@@ -60,6 +60,14 @@ This glossary defines key terms and concepts used throughout the **b** documenta
**Merge Strategy** - Controls how env file updates handle local changes. Options: `replace` (overwrite), `client` (keep local), `merge` (three-way diff).
+## O
+
+**onPost** - A per-binary shell command in `b.yaml` that runs after a successful install or update. Receives `B_EVENT`, `B_NAME`, `B_VERSION`, and `B_FILE` as environment variables. Only fires when the on-disk binary actually changed. See also: **onPreSync** / **onPostSync** below for the env-side equivalents.
+
+**onPostSync** - A per-env shell command in `b.yaml` that runs after an env file sync completes. Use it for post-sync tasks such as formatting, permission updates, or notifications related to synced env files.
+
+**onPreSync** - A per-env shell command in `b.yaml` that runs before an env file sync begins. Use it for pre-sync checks or preparation steps related to synced env files.
+
## P
**PATH** - The environment variable that tells the shell where to find executable programs.