carrick.json
Per-repo config that tells the scanner which services a repo contains and which env vars and domains name internal services versus third-party APIs.
carrick.json is a small JSON file you check in at the root of each indexed repo. It tells the Rust scanner how to classify outbound HTTP calls that were built from environment variables or URL prefixes. Without it, calls like fetch(${process.env.USER_SERVICE_URL}/users) cannot be resolved at scan time, and contract checking has nothing to compare them against.
This is different from your agent instruction file (AGENTS.md or CLAUDE.md). That file tells your coding agent when to call Carrick; carrick.json tells the scanner how to classify outbound calls. See Connecting your agent for the agent side.
Why it exists
When the scanner sees a call expression like fetch(process.env.ORDER_SERVICE_URL + '/orders'), it has the HTTP method, but not the destination. The env var might point at another service in your org (in which case Carrick should validate the call against the index) or at a third-party API like Stripe or GitHub (in which case Carrick should ignore the call entirely).
Carrick can’t guess which is which, so you declare it once per env var.
When you have not classified an env var, the action surfaces it as a warning in the PR comment:
Unclassified env var: POST /orders using [ORDER_SERVICE_URL] (from src/orders.ts) — add to
internalEnvVarsorexternalEnvVarsin carrick.json
Resolve the warning by adding the env var to the appropriate list and committing the file.
Schema
{
"serviceName": "order-service",
"internalEnvVars": ["USER_SERVICE_URL", "INVENTORY_API"],
"externalEnvVars": ["STRIPE_API", "GITHUB_API"],
"internalDomains": ["https://api.yourcompany.com"],
"externalDomains": ["https://api.stripe.com", "https://api.github.com"]
}
| Field | Type | Purpose |
|---|---|---|
serviceName | string | Friendly name for this service, used in MCP responses and the dashboard. Defaults to the repo name if omitted. |
internalEnvVars | string array | Env var names whose values point at other services in your org. Calls built from these are matched against the index. |
externalEnvVars | string array | Env var names whose values point at third-party APIs. Calls built from these are ignored by contract checking. |
internalDomains | string array | Full URL prefixes for internal services. Calls whose target URL starts with one of these are matched against the index. |
externalDomains | string array | Full URL prefixes for third-party APIs. Calls whose target URL starts with one of these are ignored. |
All fields are optional. An empty carrick.json is valid but does nothing; the unclassified-env-var report will fire on every run until you populate at least one list.
The shape above describes a single service. If your repo holds several, declare them in a services array instead — see Monorepos below.
Env vars vs. domains
Both env-var matching and domain matching work. Pick whichever fits how your code constructs URLs:
- Env vars catch indirect patterns:
fetch(${process.env.USER_SERVICE_URL}/users/${id}). You declare the env-var name, and the scanner classifies every call that uses it regardless of how the path is composed. - Domains catch URL literals:
fetch('https://api.stripe.com/v1/charges'). You declare a prefix, and the scanner classifies any call whose target URL starts with that prefix.
Most production codebases lean on env vars for internal services and inline literals or SDKs for third-party APIs, so the typical mix is internalEnvVars for your own services and externalDomains for the third parties.
Monorepos: multiple services in one repo
If a repo holds more than one service — each with its own package.json — declare them in a top-level services array instead of the flat fields above. Each entry is scanned, type-checked, and indexed as its own service, so contract checks and dependency analysis run between them rather than flattening the repo into one.
Write a single carrick.json at the repo root. Carrick reads only the config at the scan root; a carrick.json placed inside a subdirectory is ignored.
{
"services": [
{
"name": "api",
"directory": "services/api",
"include": ["packages/shared"],
"tsconfig": "tsconfig.json",
"internalEnvVars": ["WORKER_URL"]
},
{
"name": "worker",
"directory": "services/worker",
"include": ["packages/shared"]
},
{
"name": "web",
"directory": "apps/web",
"tsconfig": "tsconfig.json",
"externalDomains": ["https://api.stripe.com"]
}
]
}
Each entry takes the same classification fields as the flat form (internalEnvVars, externalEnvVars, internalDomains, externalDomains), plus a few that place the service in the tree:
| Field | Type | Purpose |
|---|---|---|
name | string | Service name, used in MCP responses and the dashboard. (serviceName is also accepted.) There is no per-entry default: an unnamed entry falls back to the repo name, so two unnamed services in the same repo collide and clobber each other in the index. Give every entry a distinct name. |
directory | string | The service’s root, relative to carrick.json. Only files under here are attributed to the service. |
include | string array | Extra source roots to resolve against — e.g. a shared library the service imports. Relative to carrick.json. |
tsconfig | string | Path to the service’s tsconfig.json, relative to directory. Scopes type extraction to that service’s project. |
When services is present, the top-level flat fields are ignored — put each service’s classification inside its own entry. Files that fall outside every declared directory are not indexed, and node_modules, build output (dist, build, .next), and test code are always skipped.
For a polyrepo — each service in its own GitHub repo — you don’t need services: use the flat form, one carrick.json per repo.
Example: a service with mixed dependencies
{
"serviceName": "checkout",
"internalEnvVars": [
"ORDER_SERVICE_URL",
"INVENTORY_SERVICE_URL",
"USER_SERVICE_URL"
],
"externalEnvVars": [
"STRIPE_API_BASE"
],
"externalDomains": [
"https://api.sendgrid.com",
"https://hooks.slack.com"
]
}
The checkout service in this example talks to three internal services (declared by env var), to Stripe (declared by env var because the base URL is configured per environment), and to SendGrid and Slack (declared by domain because the URLs are literal in source).
Related
- Quickstart. Where carrick.json fits into the initial setup.
- PR comments. Where the unclassified-env-var warning appears.