Beta

Start here

Connect Claude Code

Add the Agentower hook to Claude Code on your computer, step by step, and check it works.

You'll need an ingest key from Keys & connections (see Getting started). The Keys & connections page shows these same steps with your key and address filled in, so the quickest route is to copy from there. This page explains each step.

#Quickest: one command

On macOS or Linux, create an ingest key (on the Agents board of a new workspace, or on Keys & connections) and run this in Terminal, with your own key:

Terminal
curl -fsSL https://agentower.com/install | sh -s -- at_live_your_key_here

Then start Claude Code in any project (claude). It appears on your board within seconds. Without the key on the end, the command asks for it and keeps what you type hidden.

The command contains your key, so only run it on your own computer. It does the same as steps 1 and 2 below, and nothing more:

  • Refuses to run as root, and stops on anything other than macOS or Linux.
  • Creates ~/.agentower (only you can open it) and writes ~/.agentower/claude-code.env with the address, your key and AGENTOWER_REMOTE=off. If that file already exists, it keeps a timestamped backup beside it and keeps your AGENTOWER_REMOTE line.
  • Downloads agentower-hook.sh and agentower-statusline.sh over HTTPS into ~/.agentower, and checks each is a script before putting it in place.
  • Adds the Agentower hook entries to ~/.claude/settings.json after saving a backup of it (settings.json.before-agentower-…), keeps everything else in the file, and adds the plan-limits status line only if you have none. Run it again and nothing is duplicated. If settings.json isn't valid JSON, it is left alone.
  • Switches on token usage: asks Agentower for a telemetry key (it can only send usage numbers — your key itself never goes into settings.json) and adds Claude Code's telemetry settings with it, as in step 3. Claude Code then sends token counts, the model and the cost of each request — never your prompts or code. Running it again replaces that telemetry key rather than adding another. If Claude Code already sends its telemetry somewhere else (your own monitoring), it leaves that alone and says so.
  • Sends one request to check the key, and tells you plainly if Agentower can't be reached or refuses the key. It never prints your key.

To leave token usage off, add --no-usage: curl -fsSL https://agentower.com/install | sh -s -- at_YOUR_KEY --no-usage. Running that later also takes out the telemetry settings it added. The steps below are the same setup by hand.

#1. Save the address and key

Paste this into Terminal (Git Bash on Windows), with your own key. It creates a settings file that only you can read.

Terminal
mkdir -p ~/.agentower && chmod 700 ~/.agentower
cat > ~/.agentower/claude-code.env <<'CONF'
AGENTOWER_URL=https://ingest.agentower.com
AGENTOWER_TOKEN=at_live_your_key_here
AGENTOWER_TELEMETRY_TOKEN=at_live_your_telemetry_key_here
# Answers from Agentower on this computer: off (nobody, the default), own (only you) or team (approvers too)
AGENTOWER_REMOTE=off
CONF
chmod 600 ~/.agentower/claude-code.env
  • AGENTOWER_URL is Agentower's address, shown on Keys & connections. It must start with https:// — the hook won't send your key over plain http://, except to localhost on your own computer.
  • AGENTOWER_TELEMETRY_TOKEN is optional: a telemetry key, used only for tokens and cost — see step 3. Leave the line out if you haven't made one. Once you have, the snippet on Keys & connections includes it.
  • AGENTOWER_REMOTE says whether Agentower may answer the agents on this computer — see Answers from Agentower on this computer.

This file wins over the environment. When ~/.agentower/claude-code.env exists, the hook takes the address, the key and AGENTOWER_REMOTE only from it, and ignores any AGENTOWER_URL, AGENTOWER_TOKEN or AGENTOWER_REMOTE set in the environment (a project's own settings can set those). Without the file, the hook reads them from the environment.

The hook never puts your key on a command line, where other programs on the computer could read it: it keeps a copy in ~/.agentower/.auth-header, readable only by you, and rewrites it when the key changes. (With a version of curl older than 7.55, which can't read headers from a file, it passes the key as before.)

#Answers from Agentower on this computer

Each computer decides for itself whether anyone can answer its agents from the Agentower app. Set AGENTOWER_REMOTE in ~/.agentower/claude-code.env. The environment counts only when that file doesn't exist: with the file there, a missing AGENTOWER_REMOTE line means off, whatever the environment says.

ValueWhat it means
offNothing from the app reaches agents on this computer: no Allow or Deny, no messages. The app still shows them; answer in the terminal. This is the default — leaving the line out of the file, or any other value, means off.
ownOnly you — the person whose key this is, signed in to Agentower on any device (your phone, say) — can allow, deny or message these agents.
teamThe project's approvers (and owners) can too.

It works together with the workspace's Agent chats setting: both must allow it. A change counts from each agent's next report to Agentower (a few seconds while it works); the optional channel reads it when its session starts. The same setting covers Codex on this computer.

If you set up this computer before this setting existed, it counts as off until you add the line.

This is the gate, and it stays on the computer. Settings → Computers in the app has a switch per computer that can turn answers off at once and back on — but only up to what the computer allows here. The app can never turn on a computer that's off here, and never turn own into team. To allow answers on a computer that's off, add the line on that computer once (the app shows the command to copy):

Terminal
printf '\nAGENTOWER_REMOTE=own\n' >> ~/.agentower/claude-code.env

From the Agentower repo, node scripts/claude-code.mjs connect --remote=own does the same. With the plugin, set its Answers from Agentower on this computer option to own instead. The computer's next report turns the switch on in the app.

With the Agentower plugin for Claude Code (version 0.3.0 or later), its option Answers from Agentower on this computer (remote_answers: off, own or team; off unless you change it) is used instead, and wins over the file and the environment.

#2. Install the hook

Download the hook script from the Download the hook script link on Keys & connections (the latest is version 13, or plugin 0.4.7 — Settings → Computers → Check connection tells you if yours is older), then move it into place:

Terminal
mv ~/Downloads/agentower-hook.sh ~/.agentower/agentower-hook.sh

It's a short, readable shell script. It only uses tools that come with macOS, Linux and Git for Windows (sh, curl, grep, sed, git, and perl for a few optional features). It always exits straight away and gives up after 3 seconds if Agentower can't be reached, so Claude Code never waits on it. If a send fails (no network, or Agentower busy or unavailable), hook version 13 (plugin 0.4.7) keeps the event in ~/.agentower/spool (readable only by you; at most 200 events or 256 KB, oldest dropped first) and sends up to 20 of them, in order, with the next event — so the board catches up without ever making the agent wait.

Then add these hook entries to ~/.claude/settings.json. If you already have hooks, merge them: add the Agentower entries to each event's list and keep yours.

~/.claude/settings.json
{
  "hooks": {
    "SessionStart": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] }],
    "SessionEnd": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] }],
    "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] }],
    "PreToolUse": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] },{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\" guard", "timeout": 5 }] }],
    "PostToolUse": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] }],
    "PostToolUseFailure": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] }],
    "PermissionRequest": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] },{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\" ask-approval", "timeout": 3600 }] }],
    "Notification": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] }],
    "Elicitation": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] }],
    "ElicitationResult": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] }],
    "Stop": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] },{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\" ask-reply", "asyncRewake": true, "timeout": 14460 }] }],
    "StopFailure": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] }],
    "SubagentStart": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] }],
    "SubagentStop": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] }],
    "PostModelSwitch": [{ "hooks": [{ "type": "command", "command": "sh \"$HOME/.agentower/agentower-hook.sh\"", "async": true, "timeout": 10 }] }]
  }
}

What the entries do:

  • Every event (async) reports status: started, working, needs you, idle, failed, ended. These run in the background.
  • guard on PreToolUse checks whether a guardrail has paused the session. It only asks Agentower when your team has a guardrail switched on, and carries on if Agentower doesn't answer within 2 seconds.
  • ask-approval on PermissionRequest and ask-reply on Stop let people answer from Agentower. They do nothing unless your workspace has Agent chats switched on and this computer's AGENTOWER_REMOTE is own or team.

#3. Optional: tokens and cost

Claude Code's own telemetry adds each agent's token use and an estimated cost (at API prices). Add this env block to the same settings.json:

~/.claude/settings.json
{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_METRICS_INCLUDE_ACCOUNT_UUID": "false",
    "OTEL_LOGS_EXPORTER": "otlp",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "https://ingest.agentower.com",
    "OTEL_EXPORTER_OTLP_HEADERS": "authorization=Bearer at_live_your_telemetry_key_here",
    "OTEL_LOGS_EXPORT_INTERVAL": "60000",
    "OTEL_METRIC_EXPORT_INTERVAL": "60000"
  }
}

Agentower reads only the usage numbers from this telemetry and ignores everything else in it. The two …_EXPORT_INTERVAL lines make Claude Code send its telemetry once a minute (60,000 milliseconds).

Commands an agent runs can read settings.json, so the key in OTEL_EXPORTER_OTLP_HEADERS should be a telemetry key: it can only send OpenTelemetry (/v1/logs, /v1/metrics, /v1/traces), and every other route refuses it. Make one on Keys & connections (step 4, Create a telemetry key) — the settings block shown there then includes it, with the two intervals. An ingest key works here too, but it can do much more if it leaks.

The setup command from the Agentower repo (node scripts/claude-code.mjs connect) writes this env block for you, with both intervals set to 60000. It uses AGENTOWER_TELEMETRY_TOKEN from ~/.agentower/claude-code.env; without that line it falls back to your ingest key and warns you. The Agentower plugin doesn't set up telemetry: add this block yourself if you use the plugin. See Keys and scopes.

#4. Optional: plan limits

To see your Claude plan's 5-hour and weekly use, add the Agentower status line — see Plan limits.

#Optional: messages straight into the session (Channels)

Normally a message you send from Agentower waits for the hook: it reaches an idle agent within seconds if the agent is listening, or arrives when it finishes its current step. With the Agentower channel for Claude Code Channels, messages go straight into the session, even while it's idle.

Channels are a Claude Code research preview. The channel needs Node 22 or later, and each session has to be started with a flag:

  • With the Agentower plugin:

``bash claude --dangerously-load-development-channels plugin:agentower@agentower ``

  • With the hook installed by hand — once, register the channel:

``bash claude mcp add --scope user agentower -- node ~/.agentower/agentower-channel.mjs ``

then start each session with:

``bash claude --dangerously-load-development-channels server:agentower ``

Started without the flag, nothing changes: messages are delivered by the hook as before. The channel also follows this computer's setting: with off it stays idle, and with own it passes on only messages you wrote yourself.

What stays the same:

  • A message is still a request, not your consent — the agent may still ask in the terminal before steps it wants your say-so on. See A message is a request, not a yes.
  • Permission requests work as before: answer in the terminal, or in Agentower on your computer or phone — the first answer wins.
  • The channel only ever receives messages for its own session, and sends nothing to Agentower.

Limits, for safety:

  • The ingest key must belong to a person (keys made in Keys & connections do); otherwise the channel is refused.
  • A channel belongs to whoever runs the session: only that person can open it.
  • At most 20 channels per person can be open at once.
  • The Agentower address must use https:// (http:// works only for localhost).

#5. Check it works

Start a new Claude Code session (hooks load when a session starts) in any repository. It appears on the Agents board within a few seconds, in a project named after the repository.

To see exactly what the hook would send without sending anything, run it by hand with AGENTOWER_DRY_RUN=1:

Terminal
echo '{"session_id":"test-1","hook_event_name":"SessionStart","cwd":"'"$PWD"'"}' \
  | AGENTOWER_DRY_RUN=1 sh ~/.agentower/agentower-hook.sh

It prints the folder it looked at, then the one line of JSON it would send — the event, the session ID, the project name, your operating system type, the hook's version, how old its settings from Agentower are, and a time. Nothing is sent.

Then open Settings → Computers in the app: this computer appears there (as "Mac 1", "Linux 1" or "Windows 1" until you rename it) once the hook has reported. Check connection lists what Agentower has heard from it — hook events, telemetry, the status line, the hook's version, answers from Agentower, the channel, the key and the hook's settings — and Send test checks the round trip: the hook answers on its next settings fetch, about once a minute while an agent works there. See Check a computer's connection.

#This computer's id

The first time the hook (or the status line, or the channel) runs, it makes a random id for this computer and keeps it in ~/.agentower/computer-id, readable only by you. It goes with each report so Agentower can tell your computers apart. It's 32 random characters — never your computer's name. Deleting the file just makes the computer appear as a new one.

#The work record and its turn files

The work record — files changed, lines added and removed, tests, commits, pushes and pull requests, turn by turn — needs hook version 4 or later, and perl (it comes with macOS, most Linux systems and Git for Windows; without it the hook sends no work record, and the agent's status still works). Work streams, and the instruction and the agent's summary for projects with Work details on, need hook version 6 — with the plugin, version 0.4.0 or later. The card's activity words ("Running tests", "Reading files (src)") need hook version 9 — plugin 0.4.3 — and perl too; without them the card says what it's doing from the tool's name alone ("Running a command"). If a computer runs an older hook than the current one, Check connection on Settings → Computers marks Hook version as Fail: download the hook again from Keys & connections, or update the plugin.

#Name the work: AGENTOWER_LABEL

To give an agent's work stream a name of your own, start it with a label:

bash
AGENTOWER_LABEL="landing page" claude

The hook (from the file or the plugin) reads it from the session's environment and sends it as a name only. A launch label beats every other way of naming the stream.

To number each turn and count a file once per turn, the hook keeps a small record per session in ~/.agentower/turns/<session>, readable only by you (permissions 0600). It holds the turn number, running totals and a scrambled fingerprint of each file changed — numbers and fingerprints only, never file names or code. Records not touched for two days are removed.

#Sessions in a folder of projects

The project name comes from the repository's origin remote, or else the repository's or folder's name. If you start a session in a folder that just holds projects — your home folder, or a folder with two or more repositories inside — no name is sent and the agent goes under Unsorted. To mark other folders like that, list them (separated by :) in AGENTOWER_UNSORTED_DIRS in ~/.agentower/claude-code.env.

#Disconnect

Remove the Agentower entries from ~/.claude/settings.json (every command that mentions agentower-hook.sh, and the env lines above), then revoke the key on Keys & connections. Revoked keys stop working within a minute.