> ## Documentation Index
> Fetch the complete documentation index at: https://docs.outerlayer.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# The config file

> Every key in ~/.outerlayer/config.json.

`outerlayer login` creates `~/.outerlayer/config.json` with owner-only permissions. Every write merges into it, so a key you add by hand survives a later `login`. Remove a key by editing the file.

A `login` that is waiting for approval keeps its one-time key in `~/.outerlayer/login-pending/<id>.json`, readable by you only. `login` and `login --check` remove it when the login ends, and remove any that has passed its expiry.

```json theme={"system"}
{
  "url": "https://gateway.example.com",
  "apiKey": "<key>",
  "appId": "<factory-id>",
  "autoSync": true,
  "tier": "full",
  "repos": { "include": ["github.com/acme/*"], "exclude": ["github.com/acme/secrets"] },
  "scrub": { "literals": ["hunter2"], "patterns": [{ "label": "ticket", "pattern": "ACME-[0-9]{6}" }] },
  "runner": {
    "host": "ci-3",
    "queues": ["implement", "amend"],
    "concurrency": 1,
    "pollSeconds": 60,
    "timeLimitMinutes": 480,
    "idleLimitMinutes": 30,
    "commands": { "implement": "claude -p \"/build\" --permission-mode bypassPermissions", "amend": "claude -p \"/amend\" --permission-mode bypassPermissions" },
    "hooks": { "provision": "/opt/outerlayer/provision.sh", "cleanup": "/opt/outerlayer/cleanup.sh", "report": "/opt/outerlayer/report.sh" }
  }
}
```

| Key | Default | Meaning |
| - | - | - |
| `url` | none | Gateway base URL. Set by `login`, or by `login --url` with a piped key. With no `--url` and no `OUTERLAYER_URL`, a cloud command refuses without it. |
| `apiKey` | none | The factory key, set by a piped `login`. It wins over `accountToken`. Never printed back. |
| `appId` | none | The factory id of a factory key, set by `login --app-id`. With an account login instead of a factory key, commands ignore it and use the factory the repository is connected to (`outerlayer connect`). |
| `accountToken` | none | Your account's login token, used only when no factory key is set. Never printed back. `logout` removes it. |
| `dashboard` | none | The dashboard `login` signed in to, so `logout` can revoke the token there. |
| `autoSync` | `true` once the file exists | Whether the hooks run `sync --quiet` in the background and the daemon streams launched sessions. `false` leaves every upload to you. |
| `tier` | `"full"` | Capture tier for a sync without `--tier`: `metrics`, `redacted` or `full`. |
| `repos` | none | Which repositories may sync, and which the runner takes work in. Patterns match the host-qualified remote, such as `github.com/acme/app`. `*` matches anything, and matching ignores case. `exclude` wins over `include`. With `include` set, a session with no remote is skipped. An item whose tracker names no repository, such as Linear or Jira, matches no pattern. An excluded session is skipped for good; loosen the filter, then run `sync --all` to pick it up. |
| `scrub` | none | Extra scrubbers on top of the built-in secret patterns. `literals` are exact strings. `patterns` are regular expressions, each with an optional `label` shown in the redaction marker. |
| `runnerVm` | none | Written by `outerlayer runner init --vm` on a Mac. `instance` is the Lima VM the runner lives in; `since` is when it was made. While set, `doctor` reports the VM and `runner init --vm` refuses to make another. See [Running on macOS](/run-builds-on-macos). |
| `runner.host` | this machine's hostname | The name claims are recorded under. Two config files on one machine need distinct hosts, or both runners can claim the same item. |
| `runner.queues` | both | Which of `implement` and `amend` this runner takes work from. |
| `runner.concurrency` | `1` | Jobs running at once. A whole number of at least 1. |
| `runner.pollSeconds` | `60` | How often the runner looks for new work. From 5 to 86400. |
| `runner.timeLimitMinutes` | `480` | A job past this age ends `timed_out`. Also the ceiling on any one hook. At most 1320. |
| `runner.idleLimitMinutes` | `30` | A job whose log stops growing for this long, using almost no CPU, ends `idle`. It is also how long a hook may write nothing to its own log before the runner ends it. |
| `runner.commands.implement` / `.amend` | none | The command line that starts the agent for that queue. Runs through `/bin/sh -c`, so `$OUTERLAYER_WORK` expands. Required for every queue in `runner.queues`. |
| `runner.hooks.provision` / `.cleanup` | none | Run before and after the command. Both required. Either an absolute path to an executable, with no arguments and no `$HOME` or `~`, or the same built-in for both: `builtin:container` ([Container builds](/container-builds)), `builtin:vm` ([MicroVM builds](/container-builds#microvm-builds)) or `builtin:process` ([The process fallback](/write-your-own-hooks#the-process-fallback)). |
| `runner.hooks.report` | none | Optional executable, same path rule, run when a job starts and ends. It cannot be a built-in. |
| `runner.autoUpdate` | `true` when both hooks are built-in, `false` when you supply your own | Whether the runner updates itself to the CLI version its gateway names. It installs each version under `<config dir>/runner/versions/<version>/`. A version that fails its checks is rolled back to the previous one. A host with no install layout cannot update itself until you run `outerlayer runner install`. See [Keep the runner up to date](/run-a-host-as-a-service#keep-the-runner-up-to-date). |
| `runner.build.memory` | `8g` | Container memory limit, with no swap. A number with an optional `k`, `m` or `g`, such as `8g` or `1.5g`. Also the provision hook's `OUTERLAYER_MEMORY`, and the limit of the runner's own image builder. |
| `runner.build.cpus` | `4` | Container CPU limit, above 0. A container build also sees only this many cores, rounded up. Also `OUTERLAYER_CPUS`. |
| `runner.build.pids` | `4096` | Container process limit, at least 1. Also `OUTERLAYER_PIDS`. |
| `runner.build.disk` | `40g` | The most the build's volume may hold. A build past it is released `failed` with `disk_limit_exceeded`. Same form as `memory`. Also `OUTERLAYER_DISK`. |
| `runner.build.variables` | `[]` | Container builds only. Up to 100 names of host variables passed into the build, the only way one enters it. A name the runner sets itself (`OUTERLAYER_*`, `PATH`, `ANTHROPIC_BASE_URL` and the like) is refused. `GH_TOKEN`, `GITHUB_TOKEN`, `GH_ENTERPRISE_TOKEN` and `GITHUB_ENTERPRISE_TOKEN` are skipped and logged. `CLAUDE_CODE_OAUTH_TOKEN` and `ANTHROPIC_API_KEY` are accepted, but the build gets a placeholder, never the value. |
| `runner.build.allowHosts` | none (every public host) | The public hosts a build may reach through the tunnel. Each entry is a host name, an IP address, or `*.` and at least two labels, which matches names under that domain but not the domain itself. At most 500. See [Limiting the hosts a build reaches](/container-builds#limiting-the-hosts-a-build-reaches). |
| `runner.housekeeping.jobsKeepDays` | `30` | Whole days a finished build's job directory is kept after it last changed, at least 1. An unfinished build's directory is never removed. See [Housekeeping](/run-a-host-as-a-service#housekeeping). |
| `runner.housekeeping.buildCacheMax` | `"20g"` | The most the runner's own image builder keeps in its cache, in the same form as `runner.build.memory`, or `"off"` to leave the cache alone. |
| `runner.housekeeping.minFreeDisk` | `runner.build.disk` | Free space the runner's file system must hold for the runner to claim work, in the same form as `runner.build.memory`. Below it the runner claims nothing until there is room. |
| `runner.build.destinations` | `[]` | Credentialed services the runner forwards a build's requests to, with the secret kept on the host. At most 18. `gateway` and `claude` are built in and refused as names. See [Destinations](/destinations-and-secrets). |
| `runner.build.destinations[].name` | none | 1 to 63 lower-case letters, digits and hyphens, unique. Required. |
| `runner.build.destinations[].upstream` | none | The service's `https://` address, with no login, query or fragment. Required. |
| `runner.build.destinations[].header` | none | `Name: value`, set on every forwarded request. The value may read the runner's environment as `${NAME}`. The runner refuses to start while one is unset. A name may not also be in `runner.build.variables`. Required. |
| `runner.build.destinations[].env` | `{}` | Variables set in the build to point its tools at the destination. `{local}` in a value is the destination's local address. A name the runner sets, or another destination sets, is refused. |
| `runner.checks.timeLimitMinutes` | `10` | From 1 to 240. The longest the host checks' environment may take to set up, and the longest one check command may run. A check past it is recorded as a fail. See [Host checks](/reference/cli-runner#host-checks). |

The runner needs `url`, `apiKey` and `appId` from a factory key, never a
person's login. Pipe a factory key into `outerlayer login` for its config
file first. Every `outerlayer runner` subcommand takes `--config <path>` to use
a file other than `~/.outerlayer/config.json`.

Every key inside `runner`, `runner.build`, `runner.housekeeping` and
`runner.checks` is checked when the runner
starts and whenever the file changes. An unknown key is refused, naming the
key it is most likely a misspelling of. `outerlayer runner check` reports
all of this without taking any work.

See [`outerlayer runner`](/reference/cli-runner) for what each hook receives
and is expected to do.

Environment variables override `url`, `apiKey`, `appId` and `tier`. The runner ignores them and reads only its own config file. See [Environment variables](/reference/environment-variables).

## Other files under \~/.outerlayer

| Path | What it holds |
| - | - |
| `spool/` | Launch records, artifact spool, hook errors. `spool/hook-errors.log` explains an issue that did not reach the Work page. |
| `statusline.json` | The cross-agent figures the status line reads. Kept fresh by the daemon. |
| `cli/` | The installed copy of the CLI that the hooks execute. `doctor` warns when it is older than the workspace build. |
| `login-pending/` | A login waiting for approval. See the top of this page. |
| `hooks-manifest.json` | The hooks `init` installed and where. `doctor` compares it with the settings files to spot hooks that went missing. |
| `last-sync.json` | When the last sync ran, how it ended, and when one last succeeded. The session hooks warn when sync has been failing. |
| `watermarks.json` | For each gateway and factory, the newest transcript already synced, so a sync reads only what is newer. |
| `reported-cost/` | The session cost Claude Code reports to the status line, one file per session, for the uploads to read. |
| `control-planes/` | Cached clones of the [context source](/share-instructions-across-repositories) a governed repository reads. |
| `runner/` | The runner's files. See [Paths](/reference/cli-runner#paths). |
| `runner.env` | The runner service's Claude credential, read only when the service starts. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.