# Troubleshooting

Start with the symptom you can observe. Avoid changing several parts of the
installation at once.

- [Installation and authentication](#installation-and-authentication)
- [Enforcement](#enforcement)
- [Expected pauses](#expected-pauses)
- [Pull requests and Plan checks](#pull-requests-and-plan-checks)
- [Custom Loops](#custom-loops)
- [Updates and support](#updates-and-support)

## Installation and authentication

### Until guidance does not activate

**Likely cause:** the plugin is not installed, was installed from the wrong
directory, or the editor was not restarted.

**Try:**

1. Confirm the Until plugin is installed and enabled. See
   [Set up Until](setup.md) for platform-specific steps.
2. For Cursor, confirm the local plugin or symlink still points to the cloned
   repository.
3. Restart Claude Code or OpenCode, reload Cursor with **Developer: Reload
   Window**, restart Factory Droid, restart Antigravity CLI (`agy`), or start a fresh Pi session.
4. Start a new conversation and ask the agent to implement a small change.
   Until should begin by shaping the change and drafting a Plan before writing
   code.

Existing conversations do not receive a new session-start hook retroactively.

### Authentication or workspace setup does not complete

**Likely cause:** MCP authentication is incomplete or the login flow was closed
before the workspace was created or selected.

**Try:**

1. In Claude Code, run `/mcp` and complete authentication.
2. In OpenCode, run `opencode mcp list`. If `until` needs authentication, run
   `opencode mcp auth until`.
3. In Cursor, open settings, find MCP, and confirm the Until server is
   authenticated.
4. In Factory Droid, complete MCP authentication when Until prompts during a
   fresh session.
5. In Antigravity CLI, run `/mcp` (or the TUI MCP auth flow) and complete
   browser sign-in. After native install, the Until callback
   `https://antigravity.google/oauth-callback` is allowed.
6. In Pi, run `/mcp` to confirm the Until server is listed, then run
   `/mcp-auth until` and complete the browser flow.
7. In Amp, run `amp mcp doctor`. On first load the Until server is often **awaiting approval** even after the plugin registered it — run `amp mcp approve until`, then check again. If `until` is missing entirely, add
   `https://run.until.dev/mcp` under Settings → MCP (website) or run
   `amp mcp add until https://run.until.dev/mcp` (CLI). Reload Amp if tools
   still do not appear after approval and OAuth.
8. Return to the same conversation after the browser flow.
9. If Until presents a setup link, use that exact link rather than guessing a
   workspace URL.
10. Start a fresh conversation after authentication if the existing session
   still cannot see the workspace.

Do not include OAuth tokens or other credentials in a support issue.

### Pi Until fails to load with MCP tool or flag conflicts

**Likely cause:** a standalone `pi-mcp-adapter` older than 2.28.0 is installed
alongside `pi-until-loop`, or it already owns the global `mcp` / `mcpScript`
tools and `--mcp-config` flag in a way Until cannot reuse.

**Try:**

1. Confirm Until skills and startup guidance still load. They should remain
   available even when MCP registration degrades.
2. Upgrade the standalone adapter to 2.28.0 or newer (`pi update npm:pi-mcp-adapter`),
   then restart Pi.
3. If you do not need a standalone adapter, remove it (`pi remove npm:pi-mcp-adapter`)
   so Until can use its bundled copy, then restart Pi.
4. Run `/mcp`. An existing server named `until` is left as configured, including
   one imported from another host. Authenticate with `/mcp-auth until` when that
   server is the Until plugin URL.

### Until MCP is not connected

**Symptom:** Until skills load but MCP tools (`submit_plan`, `get_plan`, …) are missing, or `amp mcp doctor` does not list `until`.

**Try (Antigravity CLI):** If `agy` logs `MCP server connection closed unexpectedly for until` while sending `notifications/roots/list_changed` and then skips the `mcp_servers` prompt section, Until's Streamable HTTP handler rejected a JSON-RPC notification. That is a until-backend fix, not a plugin reinstall. After the backend is deployed, start a fresh `agy` session.

**Try (Amp):**

1. Run `amp plugins list` and confirm `until` is present.
2. Run `amp mcp doctor`. If the server is **awaiting approval**, run `amp mcp approve until`.
3. If `until` is absent, add the server manually:
   - Website: Settings → MCP → add `https://run.until.dev/mcp`
   - CLI: `amp mcp add until https://run.until.dev/mcp`
4. Reload Amp or start a fresh thread after approval and OAuth complete.
5. If you cloned the plugin, confirm the directory is `until` (not `plugins`) so the entry point and skills resolve correctly. Amp does not follow a symlink of the whole plugin tree as `index.ts`.

## Enforcement

### The agent starts implementing before there is a Plan

In Claude Code, Cursor, Factory Droid, Antigravity CLI and OpenCode, deterministic enforcement is inactive in
an ordinary repository before Plan submission begins. Pre-Plan behaviour
depends on the initial Until guidance loading correctly.

**Try:**

1. Start a fresh conversation and confirm Until begins by shaping the change
   and drafting a Plan.
2. Confirm the plugin installation using
   [Until guidance does not activate](#until-guidance-does-not-activate).
3. If the repository contains a `.until-method` marker, continue with
   [Until enforcement hooks do not run](#until-enforcement-hooks-do-not-run).

In Claude Code, Cursor, Factory Droid, Antigravity CLI and OpenCode, a repository with `.until-method` is
default-closed: supported implementation changes are blocked before a Plan has
been cleared. Repositories without that marker are protected after Plan
submission or source-control setup begins, but not before.

OpenCode enforcement covers its built-in `write`, `edit`, `apply_patch` and
`bash` tools. Tools installed by other OpenCode plugins are outside this
support boundary.

Pi loads Until's startup guidance and MCP tools, but it does not load these
deterministic enforcement hooks. If a Pi agent ignores the guidance, no hook
blocks implementation, including in a repository with `.until-method`.

Amp loads startup guidance, MCP, skills and TypeScript enforcement in the
plugin. If a new thread lacks `until:using-until bootstrap for amp`, reload
the plugin (`plugins: reload`) or restart Amp. Confirm the plugin directory
is named `until` under `~/.config/amp/plugins/` or your personal plugin repo.

On Windows, the guidance may load while enforcement does not. Windows is not a
supported environment for the current hooks.

### Until enforcement hooks do not run

**Likely cause:** `python3` is not available, the OpenCode package is stale, the
Cursor user hooks are missing or incomplete, or `~/.cursor/hooks.json` points
to a clone that was moved or deleted.

**Try:**

1. Confirm `python3 --version` succeeds.
2. For OpenCode, refresh the package with `opencode plugin
   @until-dev/plugins --global --force`, restart OpenCode, and inspect
   `~/.until/hooks.log` after a supported tool call.
3. For Cursor, open `~/.cursor/hooks.json` and confirm it is valid JSON:

   ```bash
   python3 -m json.tool ~/.cursor/hooks.json
   ```

4. Confirm all three Cursor Until entries are present:
   - `afterMCPExecution` runs `until-track-state`;
   - `beforeShellExecution` runs `until-commit-gate`;
   - `preToolUse` runs `until-commit-gate`.
5. Confirm each Cursor command points to an existing executable in the current,
   permanent Until clone.
6. Inspect `~/.until/hooks.log` for recent `track-state` and `commit-gate`
   activity.
7. Reload Cursor after changing the hook configuration.

The installer reports that hooks are already present when it finds an existing
`until-commit-gate` entry. That message does not prove the installation is
complete or that its paths are current.

If paths are stale, edit them manually. Alternatively, remove the old Until
entries and then run `./hooks/install-user-hooks.sh` from the permanent clone.
Rerunning the installer while an old `until-commit-gate` entry remains does not
update it.

### Homebrew Python cannot open the hook file (EPERM)

**Symptom:** Cursor shows `Permission denied` / `Hook blocked` on every tool
(Read, Shell, Until, Linear). The hook log never shows `commit-gate fired` or
`track-state fired`. Until MCP discovery may still work.

**Likely cause:** Cursor launched Homebrew `Python.app` against a workspace hook
file using the old `#!/usr/bin/env python3` shebang. On some macOS setups the
interpreter cannot `open()` the workspace path (`Operation not permitted` /
errno 1). Cursor fail-closes when the hook process never starts.

**Try:**

1. Confirm the installed commands are the current [`until-commit-gate`](../hooks/until-commit-gate)
   and [`until-track-state`](../hooks/until-track-state) from your Until clone
   (first line should be `#!/bin/sh`, not `#!/usr/bin/env python3`).
2. Re-run `./hooks/install-user-hooks.sh` from the permanent clone if paths are
   stale, then reload Cursor.
3. Inspect `~/.until/hooks.log` for `FAIL-OPEN` lines. When the shell wrapper
   cannot run Python or Python does not return a valid allow/deny decision,
   the commit gate allows tools and shows this message on every failed start:
   “Until commit gate could not start. Tools are unrestricted until python3
   can run this hook.” That is preferable to a total agent outage.
4. Settings → Hooks is not the only recovery path; fixing the hook file or
   `python3` on `PATH` restores enforcement.

### Factory Droid enforcement hooks do not run

**Symptom:** Until skills and MCP tools load, but the agent can edit product
files or run shell commands while a Plan is in flight.

**Likely cause:** the plugin is not installed with user scope, Droid was not
restarted after installation, `python3` is missing from `PATH`, or enforcement
was bypassed because the hook failed open.

**Try:**

1. Confirm installation:

   ```bash
   droid plugin install until@until --scope user
   ```

2. Restart Droid and start a fresh session.
3. Confirm `python3` is available where Droid launches hooks. If it is not,
   the commit gate fails open and allows the tool — including `Task` — rather
   than blocking the session.
4. Inspect `~/.until/hooks.log` for recent `commit-gate fired` lines after a
   supported tool call on the **main** Droid session (`Execute`, `Create`,
   `Edit`, `ApplyPatch`, `Task`, or their Claude Code equivalents when testing
   a shared clone).

While Until is enforcing, the main Droid session denies `Task` spawns as well
as shell and file tools. An idle session in an unmarked repository may still
use `Task`.

Factory Droid on Windows is not supported for enforcement hooks.

### Antigravity CLI enforcement hooks do not run

**Symptom:** Until skills load, but MCP tools are missing, `agy` logs
`Failed to parse hooks for plugin until` / `command hook must specify
'command'`, or the agent can edit files and run shell while a Plan is in
flight. `/hooks` is the project hook editor and does not list plugin
hooks — an empty project list there is not a failed Until install.

**Likely cause:** `agy plugin install` imported Claude Code instead of the
native package, PreInvocation handlers were wrapped like tool hooks, `agy`
was not restarted, `python3` is missing from `PATH`, or the hook failed
open.

**Try:**

1. Confirm you installed the Until plugin root (the directory that contains
   `plugin.json`, `mcp_config.json`, and `hooks.json`):

   ```bash
   agy plugin uninstall until
   agy plugin install /absolute/path/to/workspace/plugins
   ```

2. Run `agy plugin list` and confirm Until is a native plugin, not a
   Claude Code import. Skills without Until tools is a failed install.
3. Restart `agy` and start a fresh session. Run `/mcp` to finish sign-in.
   After a write or `run_command`, confirm `~/.until/hooks.log` has a
   `commit-gate fired` line.
4. Confirm `python3` is available where `agy` launches hooks. If it is not,
   the commit gate fails open and allows the tool rather than blocking the
   session.
5. Inspect `~/.until/hooks.log` for recent `commit-gate fired` lines after a
   supported tool call (`run_command`, `write_to_file`,
   `replace_file_content`, `multi_replace_file_content`, `invoke_subagent`,
   or `create_file` / `edit_file` aliases).

Windows is not supported for Antigravity CLI enforcement hooks.

## Expected pauses

These states are deliberate stops in the Until Loop rather than installation
failures.

### Plan submission asks for source-control setup

This is expected the first time Until needs access to a repository. No Plan has
been submitted when the response contains no Plan ID.

**Try:**

1. Open the exact setup link Until provides.
2. Connect the correct source-control provider and repository.
3. Return to the same conversation and send `continue`.
4. The agent must retry `submit_plan` with the original details, run the
   returned upload command, and confirm the saved Plan with `get_plan`.

Until does not resume submission automatically. Do not begin implementation
until the retry, upload, and confirmation have succeeded.

If Until reports an unsupported repository provider, there is no setup link or
`continue` loop. Use a repository URL hosted by a provider supported by the
Until workspace.

### A Plan has an ID but is still saving

Receiving an `UNTIL-<number>` ID begins submission, but the Plan body still has
to be uploaded.

**Try:**

1. Ask Until for the current Plan.
2. If it is waiting for upload, retry `submit_plan` or `update_plan` to obtain a
   fresh upload command.
3. Run that exact command.
4. Confirm the saved Plan with `get_plan`.

Upload authorizations expire. Do not reuse an expired upload command.

### A Plan is ready but implementation does not start

A ready Plan makes implementation available but does not start it
automatically. Give Until a fresh instruction:

> Implement now.

See [Run your first Until Loop](first-until-loop.md#4-run).

### A Plan remains awaiting approval

**Likely cause:** approval is required and no other person has recorded an
approved decision.

**Try:**

1. Confirm that another eligible person is available in the Until workspace.
2. Ask Until to send the Plan to that reviewer.
3. Have the reviewer read the actual Plan in their own Until session.
4. Wait for their recorded decision.

Chat agreement, the Plan author's own decision, and an agent recommendation do
not satisfy required approval. See [Plan approvals](plan-approvals.md).

### A Plan review requests changes

Implementation remains paused, but the same Plan can be revised.

**Try:**

1. Discuss the requested changes and revise the Plan.
2. Read the revised Plan.
3. Call `update_plan` for the same Plan, run its upload command, and confirm the
   saved revision with `get_plan`.
4. Request a fresh human review.

Do not carry unresolved Plan decisions into implementation as TODOs.

### Implementation remains blocked after approval

The local enforcement state only clears after Until confirms the current Plan.

**Try:**

1. Ask Until to call `get_plan` for the same Plan ID.
2. Confirm the Plan upload completed.
3. Confirm the current lifecycle is approved, or that review is not required.
4. Retry implementation only after that confirmation.

## Pull requests and Plan checks

### The pull request is not linked to its Plan

**Likely cause:** the pull request does not contain a recognizable Plan ID or
the repository is not connected to the same Until workspace.

**Try:**

1. Add the friendly Plan ID to the pull request description:

   ```text
   Plan ID: UNTIL-42
   ```

2. Confirm the pull request belongs to the repository attached to the Plan.
3. Push or update the pull request so Until processes the new reference.

Until can also recognize the ID in the pull request title, branch, or commit
messages, but the description is the clearest durable reference.

### A Plan check does not start

**Likely cause:** the pull request is not linked, the repository connection is
missing, or no Plan was fully submitted.

**Try:**

1. Resolve pull request linking first.
2. Confirm the Plan upload completed.
3. Confirm the repository connection remains active.
4. Ask Until for the current Plan and pull request status.

No submitted Plan means there is nothing to check.

### A Plan check does not rerun after a push

**Try:**

1. Confirm the pull request head SHA changed.
2. Confirm the pull request remains open and linked to the Plan.
3. Ask Until for the check associated with the current pull request head.
4. Wait for an active check to complete before pushing another diagnostic
   change.

Checks are associated with a particular pull request head. A completed check
for an older commit does not describe the current implementation.

### Plan differences do not clear

**Try:**

1. Ask Until for the check associated with the current pull request head.
2. For each difference, either fix the implementation and push a new commit or
   have an authorized person acknowledge the intentional difference with a
   genuine reason.
3. Wait for the new head to be checked.
4. Confirm that no blocking unacknowledged differences remain. A check with
   acknowledged differences is neutral; only a check with no differences is
   successful.

See [Plan checks](plan-checks.md) for the full difference-resolution flow.

## Custom Loops

### A Custom Loop is missing

**Likely cause:** Until has not ingested the default-branch commit, the file is
outside the supported directory, or the front matter is invalid.

**Try:**

1. Put new loops under `.until/loops/` with a `.md` extension.
2. Confirm the file was merged into the connected repository's default branch.
3. Include `name`, `event_schema: until`, and `on` in the front matter.
4. Ask Until to list your Custom Loops and inspect any ingestion error.

See [Custom Loops](custom-loops.md).

### A Custom Loop appears but does not run

**Likely cause:** the event did not occur or the CEL filter evaluated to false.

**Try:**

1. Confirm the event name against `until://loops/syntax`.
2. Check that CEL front-matter fields use snake_case.
3. Simplify the filter while keeping it narrow enough for safe testing.
4. Trigger a new matching event in a test repository.
5. Inspect the latest Custom Loop runs and their triggering events.

Do not leave a broad unfiltered trigger in place if it would run on every event
in production.

### A Custom Loop cannot use Slack or Linear

Those integrations are optional and must be connected before a loop can use
them.

Connect the integration through Until, then trigger a new matching event. If
you do not want the integration, change the loop so it exits cleanly when the
tool is unavailable. See [Custom Loop integrations](custom-loops.md#integrations).

## Updates and support

### Updating does not change the behaviour

**Try:**

1. Confirm you updated the same clone the editor loads.
2. Reload the editor.
3. Start a fresh conversation.
4. For OpenCode, confirm `@until-dev/plugins` remains in the global `plugin`
   array and refresh it with `opencode plugin @until-dev/plugins --global
   --force`.
5. For Factory Droid, confirm the plugin is still installed with user scope,
   then restart Droid.
6. For Antigravity CLI, reinstall from the Until plugin root with
   `agy plugin install`, confirm `agy plugin list` shows a native plugin,
   then restart `agy`.
7. For Cursor, verify the existing user-hook entries and paths using
   [Until enforcement hooks do not run](#until-enforcement-hooks-do-not-run).
8. For Pi, run `pi list` to confirm the installed source. The Git installation
   is pinned to an immutable tag, so `pi update --extensions` only refreshes
   that release. To move to a later release, remove the tagged source and
   install the newer tagged source as described in [Set up Until](setup.md).

After an ordinary `git pull` in an unmoved clone, rerunning the hook installer
is unnecessary and does not rewrite existing entries.

### Opening a support issue

Include:

- Claude Code, Codex, Factory Droid, Antigravity CLI, Cursor, OpenCode, Pi or Amp and its version;
- operating system;
- Until plugin version or commit;
- the step and documentation section followed;
- the exact error with secrets removed;
- whether the failure also happens in a fresh conversation.

Open issues at
[github.com/until-dev/plugins/issues](https://github.com/until-dev/plugins/issues).
