Skip to main content
Deploy posthook to every engineer’s machine from your MDM instead of asking each person to run the install command. This is the recommended path for org-wide rollouts: engineers do nothing, hooks are in place before their next agent session, and new machines are covered as they’re enrolled. The MDM flow uses the same installer as the share-a-link install. The only differences are that the script runs as the logged-in user from a management agent, and you pass the engineer’s identity in through an environment variable because there’s no terminal to prompt on.
Posthook supports macOS and Linux (Intel and arm64). Windows isn’t supported yet, so skip Windows devices in your scope.

Before you start

1

Get a team install key

In Bilanc, a Owner or Manager goes to Admin → Connections → AI Tools → PostHook and clicks Generate install link. The command shown contains the key after apiKey=:
Copy the whole URL. The key is shown once; store it in your MDM’s secrets store or script variables, not in a wiki.The key is org-scoped and write-only — it can send posthook data into your workspace and cannot read anything. One key serves the whole organisation.
2

Decide how you'll pass the engineer's email

Posthook stamps every session with a work email so the dashboard can attribute activity to a person. Interactively, the installer asks for it. Under MDM there is no terminal, so set POSTHOOK_ENGINEER_EMAIL in the script instead.Most MDM tools expose the assigned user’s email as a script variable — use that. If yours doesn’t, you can leave it out: sessions fall back to the machine’s global git config user.email, and the engineer can set it later with posthook identity --set-email you@company.com.
3

Allow the network egress

The installer and the sync daemon need HTTPS to:Self-hosted Bilanc customers replace api.bilanc.co with their own API host. The install link your dashboard generates already points at the right place.
4

Install agents before posthook, or re-run init after

posthook init installs hooks only for agents it finds on the machine (it looks for ~/.claude, ~/.cursor, and ~/.codex). If Claude Code, Cursor, or Codex is installed later, run posthook init again — it’s idempotent. The simplest way to cover this is to make the install script a recurring policy (see Keep it current).

Install script

The install must run as the logged-in user, not root. Everything posthook touches lives in the user’s home directory: the binary in ~/.local/bin, the database and config in ~/.posthook, the agent hook files, and the per-user launchd agent or systemd unit. Running it as root installs posthook for root and captures nothing. Replace <team-key> with your key and <engineer-email> with your MDM’s user-email variable. Both are set as plain shell variables at the top of the script, so MDM parameter substitution (Jamf $4-style parameters, Kandji or Intune variables) works as normal.
MDM scripts on macOS run as root, so switch to the console user first. This is the same pattern Jamf, Kandji, and Mosyle recommend for per-user installs.
If no one is logged in the script exits cleanly so your MDM retries on the next check-in rather than marking the device failed.

What the script does

  1. Resolves the latest release from GitHub (or the version pinned in POSTHOOK_VERSION).
  2. Downloads the binary for the machine’s OS and CPU, verifies its SHA-256 checksum, and installs it to ~/.local/bin/posthook.
  3. Downloads the local dashboard bundle to ~/.posthook/dash (optional feature; skipped silently if unavailable).
  4. Writes the team key and endpoint into ~/.posthook/config.json and enables sync.
  5. Runs posthook init: installs hooks for every detected agent, installs the git shadow symlink next to the binary, and writes a global git template as a fallback.
  6. Records the engineer identity from POSTHOOK_ENGINEER_EMAIL without prompting.
  7. Installs and starts the background sync daemon: a launchd user agent named co.bilanc.posthook-sync on macOS, or a posthook-sync.service systemd user unit on Linux. It starts at login, restarts on failure, and flushes new rows every 5 seconds.
Re-running the script is safe. It upgrades the binary in place, re-runs init, keeps the existing identity and database, and restarts the daemon so the new binary takes over.

Environment variables

Set these inside the user context, before the curl … | sh line. POSTHOOK_API_KEY and POSTHOOK_CLOUD_ENDPOINT are set for you by the install link; you don’t need to set them yourself.

PATH and the git shadow

Commit capture works through a git symlink that the installer places next to the posthook binary. For it to see every commit, ~/.local/bin must come before the real git on PATH in the shells and IDE terminals engineers actually use. The installer checks this and prints a warning if it isn’t, but it never edits shell profiles. For an MDM rollout, add the path once for everyone:
Append to /etc/zshenv (read by every zsh, including non-interactive shells spawned by IDEs and agents):
If you can’t change PATH on locked-down machines, capture still works for repositories cloned or created after install, via the global git template posthook sets up. Existing checkouts need posthook track <path> run once each.
Homebrew users often have /opt/homebrew/bin prepended in their own shell profile, ahead of anything set in /etc/zshenv. posthook status reports whether the shadow is winning on that machine.

Verify the rollout

On any machine, as the engineer:
In Bilanc, open the Posthook page. Machines appear as sessions arrive, usually within a minute of an engineer’s first agent interaction after install. Sessions with no identity show under the engineer’s git email until posthook identity is set. Logs for the daemon are in ~/.posthook/sync.log.

Keep it current

Posthook does not self-update. To keep machines on the latest release, either:
  • Re-run the install script on a schedule from your MDM (weekly is plenty). The same script upgrades in place and re-detects any newly installed agents.
  • Or have engineers run posthook update, which does the same thing locally.
Pin POSTHOOK_VERSION if you want to control exactly which build ships, and bump it deliberately.

Offboarding a machine

Run as the user:
Then remove the posthook hook entries from ~/.claude/settings.json, ~/.cursor/hooks.json, and ~/.codex/config.toml if those agents remain in use, so they don’t call a binary that’s no longer there. To cut off sync for every machine at once — for example if the key leaks — revoke the install link from Admin → Connections → PostHook in Bilanc and redeploy with a freshly generated one.

Troubleshooting

Check in order: posthook sync --status for a recent successful flush and no auth error; posthook service status to confirm the daemon is loaded; posthook status to confirm hooks were installed for the agents the engineer uses. If the agent shows as not detected, it was installed after posthook — run posthook init.
The identity wasn’t set at install (no POSTHOOK_ENGINEER_EMAIL) and the machine’s git email is personal or empty. Fix on the machine with posthook identity --set-email you@company.com. Posthook retro-stamps that machine’s existing unattributed sessions when you do.
The git shadow isn’t first on PATH for the shell the engineer commits from. See PATH and the git shadow, or run posthook track <path> on the affected repositories.
The install ran as root or from a context with no GUI session, so the launchd agent couldn’t be bootstrapped into the user’s session. Re-run the install script while the user is logged in. launchctl print gui/$(id -u)/co.bilanc.posthook-sync shows the agent’s state.
The installer’s curl calls honour HTTPS_PROXY. The sync daemon is launched by launchd or systemd and does not inherit shell proxy variables, so egress to api.bilanc.co must be allowed directly. If your network requires a proxy for all outbound traffic, contact support before rolling out.