Release notes

Changelog

Every shipped version of Halton Meter, newest first.

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

v0.3.0 · 2026-05-29

Error classification ships end-to-end. The daemon normalises every provider error into seven operator-action buckets; the cloud surfaces them as `error_observations` on the Optimization Overview.

Added

  • Seven canonical error buckets — rate_limit, server_error, bad_request, auth, timeout, network, unknown. Each maps to a single operator action (back off, fix billing, wait it out, fix the request shape) regardless of provider. Anthropic, Gemini, OpenAI, and Grok/xAI all classify in this release. See /docs/concepts/error-classification.
  • Four nullable wire fields on every log record — error_class (string), provider_error_code (string), http_status (smallint), retryable (bool). No CHECK constraint or enum on error_class; the ingest tolerates unknown buckets and ignores unknown top-level fields, so a future daemon never causes ingest rejection.
  • error_observations array on the Optimization Overview. Persona-aware remediation copy splits billing_exhausted from generic auth for Solo workspaces and aggregates server_error_retryable / server_error_hard into a provider-health strip for Team and Enterprise. This aggregated surface is cloud-only.

Changed

  • The error_rate KPI predicate is now error_class IS NOT NULL OR status != 'success', counting both v0.3.0 classified errors and legacy status-only records. Additive — workspaces with no v0.3.0+ daemon traffic see no shift.
  • Two judgement calls baked into the classifier: Anthropic HTTP 529 (overloaded_error) buckets as server_error not rate_limit (provider-overload, not a per-key throttle); OpenAI HTTP 429 insufficient_quota buckets as auth not rate_limit (exhausted billing, not retryable).

v0.2.4 · 2026-05-18

Unified, IDE-agnostic attribution resolver. Closes the edge-side `unattributed` / `edge_store` leak from v0.2.3 and widens the Python pin to `<3.15`.

Added

  • IDE-agnostic workspace recovery (Cursor, VSCode, Kiro, Zed, IntelliJ). Two new attribution layers — ide_env_label (reads CURSOR_WORKSPACE_LABEL / VSCODE_WORKSPACE_LABEL from the originating process's environment) and ide_argv_label (Electron-helper trailing-token regex against argv). Both corroborate against a per-edge slug → abspath registry so a stale or moved project cannot be impersonated.
  • Per-tier attribution.tier_hit telemetry. Structured event on every tier match AND every strict-slug miss (4b_miss, 4c_miss) — lets the operator grep ~/.halton-meter/daemon.err.log to confirm which resolver tier won per row.

Changed

  • Single attribution code path. The v0.2.3 daemon-side parent-PID walk and the edge-side walk are collapsed into one attribution.resolver.resolve_unified entry point shared by the daemon's bypass path and the edge's pin path. Step 7.25 and its four helpers in tagging.py are deleted; the chromium-helper gate and IDE-family allow-list in edge_attribution.py are deleted. Attribution policy lives in exactly one place.
  • Cross-uid / cross-session walk boundary. The parent walk no longer stops on a process-name allow-list — it terminates the moment a parent's uid or controlling session differs from the originating process. Same defensive intent, applies uniformly to every IDE, no list to maintain.
  • Python pin widened from <3.14 to <3.15. Plain pipx install halton-meter now succeeds on the system interpreter when macOS's default Python is 3.14. Hard dependencies (pydantic_core, mitmproxy, aiosqlite) ship 3.14 wheels.

v0.2.3 · 2026-05-18

Two correctness fixes. Long-lived CONNECT tunnels now pin attribution past the 5-minute TTL, and macOS plist writes are atomic against same-day reinstall sequences.

Fixed

  • Attribution: pin long-lived CONNECT tunnels. Claude Code holds tunnels open for hours during a single session; once 5 minutes elapsed the edge-store row became invisible to the daemon and every subsequent request fell through to smart_default. The read-side TTL filter on attribution_store.lookup is removed; the write-side prune loop is unchanged but now passes max_age_s=86400.0 (24h) so in-flight tunnels cannot have their rows evicted.
  • Attribution: daemon-side bounded parent-PID walk for the claude family. When the originating process's cwd resolves to None or / (the production failure mode for Claude Code's Mach-O tool-loop workers), the walk consults up to 5 ancestors via psutil and re-runs the rcfile / git / mac_sandbox / workdir-basename layers against the first ancestor's cwd. Cardinal rule preserved: any exception in the walk path is caught silently and the request falls through to the next attribution layer.
  • macOS plist atomic-write hardening. Every macOS LaunchAgent plist (daemon, watchdog, edge, userenv) now writes via a single _atomic_write_bytes helper — write to <name>.tmp, fsync, os.replace over the canonical path. Fixes the same-day-reinstall race that surfaced as an 8-byte <plist/> stub on the operator's edge LaunchAgent on 2026-05-18.

v0.2.2 · 2026-05-12

End-to-end body sync. Daemon now pushes captured request and response bodies to the cloud on the same opt-in posture as metadata — disabled by default, per-project overrides win over the master switch.

Added

  • [cloud.bodies] config block. enabled (bool, default false), sync_interval_seconds (default 60), max_body_bytes_per_upload (default 524288 — 512 KiB), per_project (dict of slug → bool). Bodies do not leave the machine until bodies.enabled = true.
  • halton-meter cloud privacy set bodies.enabled true|false — flips the master body-sync switch. halton-meter cloud privacy set bodies.upload false --project SLUG drops body uploads for one project while leaving the global switch on. halton-meter cloud privacy show renders a "Body sync" section with master state, interval, byte cap, and per-project rules.
  • BodyUploader worker. Sibling to the metadata worker; reads request_bodies WHERE uploaded_at IS NULL, POSTs two envelopes per row (request + response) to POST /v1/requests/{id}/body, stamps uploaded_at on success. Pause/last-error tracked independently in cloud_body_sync_state so a body 401 doesn't pause metadata sync.
  • Schema migration 5 → 6 adds request_bodies.uploaded_at DATETIME NULL plus a supporting index. Idempotent — safe on greenfield and upgrade-in-place installs.

v0.2.1 · 2026-05-12

Patch release. Operational bugs and CLI UX polish for the `cloud` subgroup.

Changed

  • CLI UX for the cloud subgroup matches halton-meter status. Rich panels and tables replace raw dicts; cloud status adds a state banner (ACTIVE / DEGRADED / PAUSED / NOT-CONFIGURED) plus per-field health icons; cloud reconcile adds a daemon-vs-cloud variance line (zero = green check, <0.5% = yellow tolerance, otherwise red). --json flag on cloud status still emits the machine-readable shape for scripting.

Fixed

  • cloud connect --base-url <URL> now persists the URL to ~/.halton-meter/config.toml. Pre-0.2.1, the flag was a transient override — pairing would succeed but every subsequent command would read [cloud].base_url from TOML, find it empty, and report the daemon as un-paired. Now the connect flow writes base_url and enabled=true next to the 0600 credentials write.
  • halton-meter cloud reconcile no longer 422s. Daemon sent from/to query params; cloud's reconciliation router expects from_date/to_date.

v0.2.0 · 2026-05-12

First minor release. Closes the Phase 2 cloud-sync arc (daemon ↔ halton-meter-cloud) and ships upload-privacy controls. Cloud sync is strictly opt-in — the daemon ships with `cloud.enabled = false`.

Added

  • halton-meter cloud CLI group. connect, disconnect, status, whoami, sync, reconcile, pause, resume. Pairing handshake (pairing/start → user approves in dashboard → pairing/poll) mints a single-shot hm_sync_… token. Token stored in ~/.halton-meter/cloud-credentials.json (chmod 0600) with a Fernet-encrypted mirror in SQLite.
  • Cloud worker supervised as part of the daemon process. Same-process spawn alongside the proxy / API / heartbeat tasks; gated on cloud_state.enabled so users who don't pair see zero change. Crash-isolated — a cloud-side failure (transport, 401, schema mismatch) cannot block the proxy hot path.
  • Upload privacy. Tiered consent presets (minimal / standard / full) under [cloud.upload] in ~/.halton-meter/config.toml. Default is standard with source_workdir = false — the high-leak field (local path) is off by default. Per-field global overrides; per-project rules (upload = false makes a project local-only). The daemon redacts before serialisation; the cloud cannot see what the daemon never sends.
  • halton-meter cloud privacy show|set. show prints the resolved policy; set preset standard / set field.source_workdir false / set upload false --project acme-secret mutate the TOML in place (other sections preserved).
  • Observability + lifecycle. Daemon supervisor logs cloud.supervisor.spawned / cloud.supervisor.skip with structured reason. cloud_state row tracks workspace name, machine id, hostname snapshot, last-connected-at, paused_reason. Worker retry envelope: exponential backoff capped at 300s for transport + 5xx + 429 (with Retry-After honoured). 401 never retried.

v0.1.24 · 2026-05-11

Linux daemon enters public beta. Apps-mode install on Ubuntu 24.04 LTS via `pipx` + `systemd --user`. macOS unchanged.

Added

  • Linux support (public beta). halton-meter init --apps brings up two systemd --user units — halton-meter.service (daemon + mitm + FastAPI) and halton-meter-edge.service (always-on edge proxy). Verified on Ubuntu 24.04 LTS via Docker soak and AWS Lightsail with real reboot, Claude Code, and Gemini captures. Other systemd distros likely work but are not yet covered by the release gate.
  • Per-OS install paths: macOS uses launchd + Keychain trust + shell-rc edits; Linux uses systemd --user + loginctl enable-linger + certifi-bundle patching (system trust store untouched).
  • Linux operator reference at docs/DAEMON_LINUX.md in the daemon repo — every command, path, port, unit name, and endpoint cross-checked against the source.

Changed

  • Version scheme collapses from four-segment (v0.1.22.x) to three-segment (v0.1.24) ahead of the v1 cut. Internal nightly cadence continues; the public PyPI tag is the headline.
  • Daemon systemd unit declares Restart=always RestartSec=1 and an ExecStop=halton-meter reset-proxy belt-and-braces so even a SIGKILL leaves the user's network state clean.
  • halton-meter doctor edge-proxy row on Linux uses socket.connect_ex for the port-bound check — works inside containers and non-systemd environments without raising. User-facing contract is "the port is bound", not "systemd thinks the unit is running".

v0.1.22.24 · 2026-05-08

LLM host allowlist gates which traffic the edge chains through the mitm interceptor. Bundled with the v0.1.22.23 freshness-probe URL fix.

Added

  • LLM host allowlist controls which hostnames the edge proxy chains through the mitm interceptor. Non-LLM traffic bypasses interception entirely; only allowlisted provider hosts get the captured-cost treatment.
  • mitmproxy ignore_hosts is now derived as the inverse of the LLM allowlist, so the interceptor itself only attaches to traffic the daemon cares about.
  • halton-meter doctor surfaces the active intercept allowlist. One row, mono, exactly the hostnames the daemon will capture.

Fixed

  • Freshness probe URL pointed at the private haltonlabs/halton-meter repo via raw.githubusercontent.com, which returned 404 to unauthenticated clients — halton-meter doctor was silently fail-open on every install. Probe now targets https://haltonmeter.com/rates-manifest.json, hosted out of the public landing repo. (v0.1.22.23 fix, bundled into this release.)

v0.1.22.22 · 2026-05-08

First public release. Local-binary daemon, six adapters across four providers, captured-cost terminal report, bundled dashboard.

Added

  • Daemon ships as a local binary on PyPI. pipx install halton-meter followed by halton-meter init brings up the three-process architecture: edge on 127.0.0.1:8081, mitm listener on 127.0.0.1:8090, loopback API on 127.0.0.1:8765.
  • Six adapters across four providers — OpenAI, Anthropic, Gemini, Grok. Direct API surfaces and OAuth surfaces (ChatGPT, Gemini Code Assist) both intercepted without SDK changes.
  • Captured-request store: full request and response bodies, redacted, written to ~/.halton-meter/db.sqlite. Schema documented; halton-meter report slices it from the terminal.
  • Project attribution chain: env-var override, working-directory match, process-tree walk. Configured per-project via ~/.halton-meter/projects.toml.
  • Bundled dashboard at localhost:3000. Open source under Apache 2.0 (separate halton-meter-dashboard repo). Read-only; reads the daemon's loopback API.
  • CLI surface: init, start, stop, status, report, uninstall. Fail-open behaviour — when the daemon stops, traffic passes through the edge unmodified.
  • Structured logs under ~/.halton-meter/*.err.log (one pair per process). jq-queryable; documented in /docs/operations/logs.

Changed

  • Init self-test retries every 500ms for up to 30 seconds (replaces the single-shot health check). Cold-start no longer reports false negatives.