Getting started · 03 macOS only

Configure projects

Tell Halton Meter how to attribute captured requests to projects — via .haltonrc, environment variable, or the IDE workspace it sniffs at runtime.

macOS 12+ · Python 3.11+ Reading time 2 min Updated May 11, 2026

Every captured request lands in ~/.halton-meter/db.sqlite tagged with a project slug. The slug is what halton-meter report groups by, what the HTTP API splits totals on, and what shows up in cost roll-ups. Halton Meter resolves the slug through a chain of layers — each one explicit, ordered, and deterministic. This page is how you steer it.

For the architecture of the chain itself, see Project tagging.

The seven attribution layers, in order

LayerSourceWins when
1HALTON_PROJECT env varSet in the process environ
2.haltonrc walked upward from cwdA project = "<slug>" line is found
3IDE workspace (Windsurf / Cursor / VS Code cmdline sniff)The IDE was launched with --folder-uri or equivalent
4Git repo root basenameA .git directory exists upward from cwd
6.5macOS sandbox container bundle idProcess is sandboxed (e.g. mac:com.openai.chat)
7cwd basenameAlways, as a fallback
8unattributed sentinelNothing above resolved

Layers 5 and 6 are reserved for monorepo workspace detection and Linux/Kubernetes container detection respectively. Layer 5 is parked; Layer 6 is not relevant on macOS.

The pure resolver functions live in daemon/halton_meter/attribution/layers.py. Both the daemon-side and edge-side resolvers call into that module, so attribution semantics stay in lock-step across the two paths.

Pinning a slug per repo with .haltonrc

The most common configuration. Drop a .haltonrc at the root of a project; the slug applies to every cwd inside it.

~/code/billing/.haltonrc
# Halton Meter — per-project config
project = "billing"
body_capture = on

The slug is normalised to lowercase alphanumerics plus -, _, :, /. A malformed slug is rejected at parse time; the layer falls through to the next rather than silently corrupting the row.

Overriding for a single command

HALTON_PROJECT wins over everything below. Use it for one-off scripts that live outside the usual repo tree, or to split traffic from the same cwd into two slugs:

HALTON_PROJECT=experiments halton-meter run python sweep.py
HALTON_PROJECT=client-acme halton-meter run claude

How IDE workspaces resolve

When Layer 2 misses (no .haltonrc upward from cwd), Halton Meter sniffs the launching IDE’s command line for a workspace flag:

  • Windsurf / Cursor / VS Code: --folder-uri file:///path/to/repo
  • JetBrains: --project /path/to/repo

The path’s basename becomes the slug after normalisation. This lets a freshly-opened workspace get a sensible slug before you ever drop a .haltonrc.

The Layer 3 helpers — extract_workspace_path_from_cmdline(), decode_windsurf_workspace_id(), decode_file_uri() — live in the same attribution/layers.py module if you need to confirm what your IDE is actually emitting.

Inspecting and rewriting historical attributions

halton-meter project show prints the per-project settings row from SQLite:

halton-meter project show billing

To toggle body capture per-project (overrides the daemon-wide bodies.enabled switch):

halton-meter project set billing body-capture off

If a chunk of historical traffic was tagged to the wrong slug — common the first time someone moves a repo into a new directory — retag rewrites the rows in place. Defaults to dry-run:

~ — retag historical rows
$ halton-meter retag --from old-slug --to new-slug             # dry-run
$ halton-meter retag --from old-slug --to new-slug --apply    # commit

retag writes a _migrations row marking the rewrite so it never runs twice for the same from/to pair without --force.

What’s next