Set up GitHub Copilot CLI
The SDK integration records Copilot sessions and requests blocking review before tools run. One command installs it for every Copilot session of your user on this machine. It connects directly to your Watcher server. You do not need the local Watcher client.
Before you start
Use macOS or Linux on arm64 or x86_64. Linux needs glibc. Install Copilot CLI and check that it can answer a prompt. You also need Bash, curl, tar and internet access. The installer provides a private Python runtime and uv.
Use Watcher Cloud or a self-hosted server with WorkOS SSO. Your account must be allowed to ingest sessions and read its own sessions. Setup signs in as you with a login; it does not accept API keys. Company-proxy login and native Windows are not supported. The server's release must include the SDK installer and Copilot package.
Install for every Copilot session
-
Run the command for your server. You can run it from any folder.
Watcher Cloud:
curl -fsSL https://github.com/ApolloResearch/watcher-bin/releases/latest/download/install-sdk.sh | bash -s -- copilot --cloudSelf-hosted: replace the URL with your Watcher server's address.
curl -fsSL https://github.com/ApolloResearch/watcher-bin/releases/latest/download/install-sdk.sh | bash -s -- copilot --url https://watcher.acme.example -
Use the saved login offered by setup, or complete browser sign-in. When more than one saved login matches, choose one in the terminal.
-
Restart Copilot. Ask it to read a harmless file and finish its reply.
-
Run:
watcher-sdk integrations status copilotLook for Confirmed delivery and a Watcher session ID. Open that session in your server's Analyzer. A successful install or a hook callback alone does not prove recording worked. The session should show GitHub Copilot CLI; use that agent name to filter its sessions.
Existing sessions also appear as GitHub Copilot CLI when their adapter IDs remain available. This recognition needs no hook reinstall or new sign-in. The agent name is separate from the model Copilot uses.
The installer writes the hooks to Copilot's personal hooks folder, as
~/.copilot/hooks/watcher-sdk-copilot.json, or under $COPILOT_HOME/hooks/
when COPILOT_HOME is set. Copilot runs personal hooks in every folder, so
they cover each session you start. They belong to your user only: other
accounts on the machine are not covered, and you can remove them at any time.
The installer writes nothing into any project.
The installer checks server access before writing hooks. Use an HTTPS server
URL; plain HTTP works only for a server on this machine. No source checkout is
needed. The installer saves the runtime under ~/.local/share/watcher-sdk and
puts the command in ~/.local/bin. If the command is not on your PATH, use
~/.local/bin/watcher-sdk in the commands on this page.
The installer selects the SDK release that matches the server. It checks the download against the checksum published in the same GitHub release. That check catches a damaged or incomplete download, but not a compromised release. See Supply chain security for release controls.
Choose Blocking review or Record only
| Install | What it does |
|---|---|
| Blocking review, the default | Records each turn and sends each tool call to Watcher before it runs. |
Record only, with --record-only | Records each turn without reviewing tool calls. |
Your administrator's enforcement mode,
enforce, observe or paused, decides what Blocking review does. In enforce mode,
the hook withholds each call until Watcher approves it, and asks you when Watcher
escalates. In observe or paused mode, Copilot's own permissions decide.
Reinstall and update keep the selected install and login unless you choose new
ones. Use --enforce to turn Blocking review back on.
When Watcher approves a call in enforce mode, the hook answers Copilot with an
explicit allow. That skips Copilot's own permission prompt for the call, and it
overrides your Copilot deny rules, such as --deny-tool.
Trailing review runs on the server over recorded sessions, so it needs nothing from this integration.
Install in one project
To cover a single project instead, add --project /path/to/project to the
install command. The hooks then go into that project's
.github/hooks/watcher-sdk-copilot.json. In a Git repository, setup also lists
that file in the repository's local exclude file, so it is not committed. Trust
the project when Copilot asks, because Copilot runs no project hooks in an
untrusted folder. Add the same --project option to the other commands on this
page to manage that install.
When both installs cover a project
A personal install and a project install can exist together. Copilot then runs both in that project, personal hooks first, and neither stands aside for the other. Both record each turn. Each call the personal gate allows is judged by both gates. When the personal gate denies a call, or escalates it in prompt mode, Copilot does not run the project gate. Copilot withholds a call that either gate denies.
Watcher keeps one stored decision per call only when both installs send to the same Watcher organization with logins for the same user. Then, if the first gate's decision is stored as a completed judgment, Watcher answers the second gate with it instead of judging again. Any other stored decision is judged again. An install on another server or with another user's login records its own session and decision.
Install and status warn about each project that both installs cover, and print the command that removes the project install. Nothing is removed automatically. To have each call judged once, keep one install per project. Opting the project out turns off both.
Opt a project out
To keep these hooks out of a project, run:
watcher-sdk integrations opt-out copilot --project /path/to/project
The command asks you to type the project folder to confirm, and refuses when stdin or stdout is not an interactive terminal. In tests with Copilot 1.0.91, commands that Copilot's bash tool ran had no terminal, so an agent's ordinary command cannot opt its project out. A process running as you can still fake a terminal or edit the private configuration, so an opt-out is a convenience, not a control.
An opt-out takes effect from the next callback on, including in sessions already
running. In an opted-out project, every hook from this integration records
nothing and gives no decision, a project install included. Tool calls there
follow Copilot's own permissions. To cover the project again, run
watcher-sdk integrations opt-in copilot --project /path/to/project, which asks
for the same confirmation.
The choice is stored only in the personal install's private configuration,
~/.config/watcher-copilot/personal/config.json, never in the project. A file
committed to a repository therefore cannot opt it out, and you set and clear it
only with these two commands. Opting out needs the personal install, and
removing the personal install deletes its opt-outs. Inside a Git repository, an
opt-out covers the whole innermost repository. Outside Git, it covers only the
folder you name, not its subfolders. Status lists the opted-out projects.
A project install applies the opt-out only when its hooks run a version that
reads opt-outs. Opt-out and status warn when a project install's hooks run a
different Python from the one running the command, such as one installed from an
earlier release. To fix this, run
watcher-sdk integrations install copilot --project PATH again.
Choose authentication
Setup can reuse a saved SDK login or a local Watcher login for the exact server,
including its port. The Watcher app can be closed. To choose that source
explicitly, add --watcher-auth. With a custom Watcher store, also add
--watcher-state-dir /path/to/watcher-state.
To switch the hooks to a new SDK-owned login, run:
watcher-sdk integrations install copilot --cloud --login
For self-hosted SSO, replace --cloud with --url https://watcher.acme.example.
To sign in for other SDK uses without installing hooks, use
watcher-sdk auth login --cloud or watcher-sdk auth login --url URL.
Browser sign-in shows the approval link and code, then names the server you are signing in to. Approve only a sign-in you started for a server you trust, because that server receives access to your account.
New SDK logins live under ~/.config/watcher-sdk/credentials, separate from
Watcher's store. Hooks save the chosen store location, refresh tokens when
needed, and never open a browser or switch identity after a failure.
watcher-sdk auth status checks saved logins.
watcher-sdk auth logout --cloud asks the server to end the SDK cloud login's
session, then removes that login. It leaves the Watcher app's saved login and
installed hooks in place. The server may also sign out the browser that
approved the login, and a Watcher app that signed in through that browser.
API keys are not supported: a key would sit where the Copilot agent's own shell can read it, and confirming delivery with a key needs permission to read every session in the organization. Hooks from an earlier version that used an API key withhold tools and stop recording until you run the installer again with a login, which replaces them.
Check recording and stored judgments
Status reports the install's scope, its hook file, whether that file holds exactly the installed hooks and the server. For the personal install, it also lists the opted-out projects. It then reports two checks separately:
- Confirmed delivery covers the transcript only. The hooks upload each turn and read it back from Watcher.
- Stored judgments is the hooks' last read-back of the decisions Watcher stored for reviewed tool calls. It counts the decisions that matched, the ones that did not match or could not be read in time, and the ones not stored while paused. It also counts calls that were judged without storing a decision, for example when a log could not be read.
A confirmed transcript does not prove that every judgment was stored. Doctor also checks the login and server access, and reads the stored judgments back again. That read-back only reads: it never grades or judges a call again, and it leaves both your diagnostics and the stored records unchanged.
Each hook command names the server that setup used. The private
configuration names it too, and so does the login saved in it. If any of these
differ, even when only one was changed, the hooks refuse to run: tool calls are
denied, and recording fails. Status and doctor name each server, and doctor
stops before it sends anything. Run the install command again with --cloud or
--url to choose the server. Install and update repair this only against a
server you name, so a changed configuration cannot choose where the hooks sign
in. Hooks from an earlier
version name no server, so status reports that they need setup again, and
doctor fails until you run the install command again.
Maintain and troubleshoot
These commands manage the personal install. Add --project /path/to/project
for a project install:
watcher-sdk integrations doctor copilot
watcher-sdk integrations logs copilot
watcher-sdk integrations update copilot
watcher-sdk integrations remove copilot
Logs show recent event names, times and server session IDs, including failures
after Copilot exits. They contain no transcript text or credentials. Private
diagnostics live beside each install's configuration under
~/.config/watcher-copilot. Logs retain the current file and one rotated file,
each rotating at about 512 KiB.
If authentication fails, sign in again to the selected store: use watcher-sdk auth login for SDK credentials or the Watcher app for its credentials. Run
doctor again. A later completed
Copilot turn retries recording the session's full history.
Update downloads the server's matching release and updates the hooks. Add
--cloud or --url to update against a server you name.
Each install or update downloads that release, its packages and the private
Python again, then rebuilds the runtime its hooks run. This replaces any file
changed or added there. Only the uv tool is reused; it was checked when first
downloaded.
Every install reinstalls the private Python that every install on this machine shares. While it does, no hook can start. This lasts about a tenth of a second in our measurements, and longer on slow storage. With Blocking review, Copilot denies a tool call that starts in that moment. A turn whose recording callback misses it is recorded at the next successful recording callback: the end of a later turn, or the end of the session. The rebuild that follows takes longer, and it stops hooks the same way for every install on the same release until it finishes.
If another install of the same release is still running, including its sign-in, a new install waits for it to finish before it rebuilds. It reinstalls the private Python before that wait, though, so two installs at the same time, of one release or of different ones, can make one of them fail; run that one again. A failed or interrupted install leaves the old hooks usable; rerun update after fixing the problem. If an install was cut off completely, for example by a power loss, the next install first restores the runtime it was replacing when it can tell that copy finished building. If it cannot tell, it keeps both copies. Run the install command again to rebuild the runtime of that release.
Update runs code from the installed runtime. If you suspect someone changed that
runtime, run the install command again instead. To replace uv as well, first
delete ~/.local/share/watcher-sdk/runtime.
Setup and update stop rather than install a release older than the newest
finished release on this machine, whichever server it was installed for. A
release whose install was cut off before it finished building does not count.
If your servers run different versions, an install for an older server needs
--version X.Y.Z, with that server's version, on every install and update. Add
the same option to downgrade on purpose.
Removal keeps saved credentials, server records and unrelated hooks. It also retains the managed runtime, which other installs may use.
Hook limits
Personal hooks are per user and removable. They live in your own Copilot
folder, which you, and an agent running as you, can edit or delete. Project
hooks are just as editable. Neither install is an enforcement boundary against
someone, or an agent, that can change your files. The only hooks Copilot
enforces are managed policy hooks in /etc/github-copilot/policy.d/, which must
be owned by root. An administrator deploys them, for example through MDM. This
integration does not install them.
These trust and settings behaviors were measured with Copilot CLI:
- In an interactive session, Copilot asks whether to trust a folder it has not seen. Declining exits Copilot. Trusting it for the session makes the personal hooks fire for that session's prompts, turns and tool calls.
- In prompt mode, personal hooks fire in untrusted folders without a prompt.
- With Copilot 1.0.91,
disableAllHooksset in any of a repository's.github/copilot/settings.json,.github/copilot/settings.local.json,.claude/settings.jsonor.claude/settings.local.jsonstopped the personal hooks for sessions in that repository, with no warning. User-level settings did not: the same setting in yoursettings.jsonunderCOPILOT_HOMEdid not stop them in prompt or interactive mode, nor inconfig.jsonin prompt mode.
An organization policy that allows only managed hooks also stops these hooks. Setup warns about the settings it can read, but it cannot check every repository.
Watcher tool review has an inner deadline. If Copilot's outer 480-second timeout expires, execution falls back to Copilot's native permission flow.
Recording can fail even when review succeeded. Check confirmed delivery and the Analyzer, especially after session exit. These checks establish recording, not the quality of a model's grading decisions.