Skip to main content
Working with an agent? Give them a link to this page as markdown.

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​

  1. 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 --cloud

    Self-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
  2. Use the saved login offered by setup, or complete browser sign-in. When more than one saved login matches, choose one in the terminal.

  3. Restart Copilot. Ask it to read a harmless file and finish its reply.

  4. Run:

    watcher-sdk integrations status copilot

    Look 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​

InstallWhat it does
Blocking review, the defaultRecords each turn and sends each tool call to Watcher before it runs.
Record only, with --record-onlyRecords 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, disableAllHooks set in any of a repository's .github/copilot/settings.json, .github/copilot/settings.local.json, .claude/settings.json or .claude/settings.local.json stopped the personal hooks for sessions in that repository, with no warning. User-level settings did not: the same setting in your settings.json under COPILOT_HOME did not stop them in prompt or interactive mode, nor in config.json in 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.