Skip to main content
Claude Code, Cursor, and Codex run on an engineer’s laptop, so posthook installs once per machine and rides along with every agent session. Cloud agents are different: each session runs on a machine the agent vendor provisions, there is no engineer at a terminal, and the machine goes away when the session ends. Getting posthook onto those machines means using the agent’s own environment and hook mechanisms. The recipe is the same for every cloud agent, and it has three parts:
  1. Install the binary where the agent’s environment is defined, so it is on every session machine before the agent starts working. This also puts the git shadow in place, which captures commits with no further setup.
  2. Register posthook as a hook in the agent’s hook system, so the agent calls posthook ingest on every tool call and prompt. This is what produces line-level attribution and the session viewer — the git shadow alone only sees commits.
  3. Flush to Bilanc from a hook, not a daemon. Session machines don’t have a login session for launchd or systemd --user, so the background sync daemon can’t run. A hook on the agent’s stop event runs posthook sync instead.
Devin is the first cloud agent posthook supports. More will follow the same pattern.

Devin

Devin has two pieces of configuration that map onto the parts above: You need both. A blueprint can install software but can’t make Devin call it; a plugin can register hooks but can’t install software before the session starts. Putting the binary in the blueprint and the hooks in a plugin also keeps the team key off the snapshot image: the blueprint installs posthook in keyless, local-only mode, and the key is injected at runtime from a Devin Secret.
Devin support needs posthook 0.3.0 or later — earlier releases record Devin’s tool calls but don’t recognise its edit/write tool names or UserPromptSubmit, so you get no line ranges or prompts. The blueprint step below installs the latest release; check with posthook version in a session.
Devin plugins are in closed beta and plugin hooks are currently best effort: if a hook fails to load, the session continues without it. Devin’s cloud hooks also don’t fire SessionStart/SessionEnd, which is why the flush happens on Stop. The git shadow doesn’t depend on hooks, so commits are captured either way.

Before you start

1

Get a team install key

A Bilanc Owner or Manager generates one from Admin → Connections → AI Tools → PostHook → Generate install link — see Get an install link. You only need the key, the part after apiKey=. The same key your engineers’ machines use is fine; it is write-only and org-scoped.If you only want local attribution (open-source use, no Bilanc workspace), skip this — see Without a Bilanc workspace.
2

Store the key as a Devin Secret

In Devin, go to Settings → Resources → Secrets and add an organization secret named POSTHOOK_API_KEY with the team key as its value. The plugin manifest references it by name in the next section, so the key never appears in the blueprint, the plugin repo, or the snapshot.
3

Pick an identity for Devin

Posthook stamps every session with an engineer email. Devin isn’t an engineer, so give it its own: an address like devin@your-company.com makes Devin show up as a distinct “engineer” in the Posthook dashboard, and keeps its AI share separate from the humans who review its work. You’ll set it in the blueprint below.

1. Install posthook in the blueprint

Add a step to the initialize section of your organization-wide blueprint (Devin Settings → Environment → Blueprints → Organization blueprint) so every repository’s sessions get it. A repository-level blueprint works the same way if you want to start with one repo.
What this does on the snapshot:
  • Downloads the latest release, verifies its checksum, and installs it to ~/.local/bin/posthook. No key is passed, so posthook is in local-only mode and nothing is written to disk that you’d mind being in a snapshot.
  • Runs posthook init, which places the git shadow symlink at ~/.local/bin/git. The $ENVRC line puts it ahead of the system git for every later blueprint step and for the Devin session itself, so every commit Devin makes is recorded.
  • Records the identity Devin’s sessions will be attributed to.
The installer also tries to install the background sync daemon and warns that it can’t — that is expected on a Devin machine (no systemd --user bus) and is harmless: sync is handled by the Stop hook below.
Pin a release with POSTHOOK_VERSION=0.3.0 in the step’s env if you’d rather control upgrades. Otherwise each snapshot rebuild picks up the latest version.

2. Install the posthook plugin

Posthook ships a Devin plugin in its repository at plugins/devin. It’s a hooks.json that runs posthook ingest --agent devin on every tool call and prompt, and syncs when Devin stops:
plugins/devin/hooks.json
The empty matcher runs the hook for every tool. Posthook records each event and extracts line ranges from Devin’s edit, write, and apply_patch calls; UserPromptSubmit carries the prompt text; Stop fires at the end of every turn and is where pending rows are flushed to Bilanc — posthook sync first waits for the events the hooks just queued to land in the local store, so the final edit and prompt of a turn are included. Install it for the whole organisation. In Devin, go to Settings → Resources → Plugins → Configuration and add it to the manifest with the environment posthook needs for sync:
env is passed to the plugin’s command hooks in cloud sessions. POSTHOOK_CLOUD_TOKEN is read straight from the Devin Secret you created; POSTHOOK_CLOUD_ENABLED turns sync on for those commands only; POSTHOOK_CLOUD_ENDPOINT is required because the keyless install writes no cloud config — self-hosted Bilanc customers point it at their own API host instead. All three must be set or posthook sync skips the flush. New Devin sessions pick the plugin up from the manifest; sessions already running are unaffected. The entry above tracks posthook’s main branch; add "ref": "v0.3.0" (or a "sha") to pin it, matching the version pinned in the blueprint.
If you’re in an enterprise with several Devin organisations, put the plugin in the enterprise manifest so it applies everywhere. Org-level manifests only reach cloud sessions of that org.
A Devin plugin is just a git repository with a .devin-plugin/plugin.json manifest and a hooks.json at its root. Copy the two files from plugins/devin into a private repository of your own (say your-org/posthook-devin), tweak the hooks if you need to, and reference it instead with { "source": "github", "repo": "your-org/posthook-devin", "env": { ... } }.

3. Verify

Rebuild the environment snapshot (Devin does this when the blueprint changes), then start a session in any repository and ask Devin to run:
The first two work from the blueprint alone. posthook sync --status shows a successful flush once the Stop hook has fired, i.e. after Devin finishes its first turn. Sessions then appear on the Posthook page in Bilanc under the identity you chose, with the prompts, files touched, and resulting commits — the same view you get for local agents.

What gets captured

Without a Bilanc workspace

Posthook is open source and works with no key at all. In a cloud session the local SQLite database disappears with the machine, but attribution still travels with the code: after every successful git push, the shadow pushes refs/notes/posthook, so anyone who clones the repository and runs posthook blame <file> sees Devin’s lines marked as AI-written. For that, keep the blueprint step from step 1 and install the plugin without the env block. No data leaves the session machine except through your own git remote.

Devin CLI

The Devin CLI runs on an engineer’s laptop, so it is a local agent: install posthook the normal way (share a link or MDM). posthook init doesn’t write Devin CLI hooks yet, so add the same three hooks to the CLI’s user config at ~/.config/devin/config.json under a "hooks" key, or to a repository’s .devin/hooks.v1.json:
The sync daemon is already running on a laptop, so the Stop hook doesn’t need to call posthook sync. Note that the Devin CLI also reads Claude Code’s ~/.claude/settings.json: if Claude Code is installed on the same machine, Devin CLI sessions will additionally arrive via that hook and be labelled claude-code until you disable read_config_from.claude in the Devin CLI config.

Troubleshooting

The blueprint worked and the plugin didn’t. In a session, ask Devin to run posthook status: if it shows no recent devin events, the hooks aren’t firing. Check that the plugin is listed as required in the manifest for the right scope (org vs. enterprise), that the entry uses "source": "git-subdir" with "path": "plugins/devin" (a plain Bilanc/posthook entry points at the repository root, where there is no hooks.json), and that the session was started after the manifest change. Plugin hooks fail open, so a malformed hooks.json produces no error in the session.
posthook sync --status in the session shows the reason. enabled: false means POSTHOOK_CLOUD_ENABLED isn’t set in the plugin env; a blank endpoint means POSTHOOK_CLOUD_ENDPOINT is missing; a blank token or an auth error in last_error means POSTHOOK_API_KEY isn’t available to the session (check the secret’s scope and name). Note that the env only applies to the hook commands, so running posthook sync --status from Devin’s shell shows the on-disk (keyless) config — ask Devin to run it with the same variables set. Remember sync only runs on Stop, so a session that’s still mid-turn has nothing synced yet.
The git shadow isn’t first on PATH in the shell Devin commits from. Ask Devin to run which git — it should print ~/.local/bin/git. If not, the $ENVRC line in the blueprint step is missing or the snapshot hasn’t been rebuilt since you added it.
The identity wasn’t set in the blueprint, so posthook fell back to the git config user.email Devin commits with — which is often the engineer who started the session, or a bot account. Add the posthook identity --set-email line to the blueprint and rebuild.