v0.3.0 · 2026-05-29
The daemon now classifies every provider error locally into seven operator-action buckets, written to local SQLite and read by the bundled dashboard.
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 fields on every captured row —
error_class(string),provider_error_code(string),http_status(smallint),retryable(bool). Written to~/.halton-meter/db.sqliteand surfaced in the bundled dashboard alongside captured-cost rows. No enum onerror_class; unknown buckets are tolerated, so future buckets need no schema migration.
Changed
- Two judgement calls baked into the classifier: Anthropic HTTP 529 (
overloaded_error) buckets asserver_errornotrate_limit(provider-overload, not a per-key throttle); OpenAI HTTP 429insufficient_quotabuckets asauthnotrate_limit(exhausted billing, not retryable). - Backward compatible. Pre-v0.3.0 rows carry none of the four fields and remain valid; any error-rate view falls back to the legacy
status != 'success'predicate. Connect to Halton Meter Cloud to get the aggregated, persona-awareerror_observationssurface across projects.
v0.2.11 · 2026-05-24
Windows apps-mode (phase 0, public beta). Per-user proxy via HKCU registry, `certutil` for cert trust, Task Scheduler for `ONLOGON` autostart — no admin required.
Added
- Windows 10/11 apps-mode install (
halton-meter init). WritesHKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ProxyEnable=1,ProxyServer=127.0.0.1:8081,ProxyOverride="<local>", broadcastsWM_SETTINGCHANGEso running WinINet/Electron apps pick up the proxy without restart.HTTPS_PROXY/HTTP_PROXY/NO_PROXYwritten toHKCU\Environmentso new shells inherit. Snapshot of prior registry state captured to~/.halton-meter/system_state.jsonand restored onuninstall. - Per-user Task Scheduler tasks for
halton-meter daemonandhalton-meter edge(/SC ONLOGON). No admin required for HKCU tasks; the supervisor restarts on every interactive logon. - Per-user cert trust via
certutil -user -addstore Root <cert_path>— installs the mitmproxy CA into the current user'sRootstore. Removed onhalton-meter uninstall. No admin prompt.
Changed
- Documentation: Windows install ships as **public beta**. WSL2 guest traffic and Windows machine-wide (
HKLM) full-mode are deferred post-v1.0; WinHTTP-only clients (e.g. some .NET tools, Windows Update) need a one-shotnetsh winhttp import proxy source=ieafterinit.
Fixed
POST /v1/cloud/connectcancels the previous pairing task before replacing_activeso a repeatedhalton-meter cloud connect(or browserConnectbutton) cannot leave a zombie awaiting-approval task.
v0.2.10 · 2026-05-24
Attribution correctness + memory hygiene. Cached rows now carry `source_workdir`, large request/response bodies are skipped before they hit memory, and the attribution-store init race is closed.
Added
attribution_log.source_workdir TEXTcolumn (fail-openALTER TABLE). Every cached row from v0.2.10 forward carries the originating directory; historical rows showNULL. Cloud upload privacy still gates whethersource_workdiris shipped — local capture is unconditional.- Body capture size gate.
proxy.capture_body()returns early for any request or response body greater than 4 MiB and logsaddon.body_capture.skip reason=body_too_large. Prevents memory spikes from large multi-modal payloads. Metadata for the request (model, tokens, cost) still captures; only the body is dropped.
Fixed
- Attribution-store schema-init TOCTOU. Under high concurrency at process startup, the
CREATE TABLE IF NOT EXISTScould race against the firstSELECT, surfacing assqlite3.OperationalError: no such table: attribution_log. The init path now holds an exclusive lock for the duration of schema creation. - Log event naming.
cloud.worker.sync_pausedevent was emitted withreason="paused_unauthorised"even on the 403 path; now distinguishespaused_unauthorised(401) frompaused_forbidden(403).
Removed
- Dead code:
psutil.net_connections()scan intagging._get_process_name. The path was never reached in production and was a slow per-call socket walk on macOS. Removed without behaviour change.
v0.2.9 · 2026-05-24
Daemon hardening: loopback bind guard, dependency upper-bound caps, log rotation, and the report row cap is gone.
Added
- Daemon log rotation.
~/.halton-meter/daemon.out.loganddaemon.err.logrotate at 100 MiB with 5 backups (logging.handlers.RotatingFileHandler). Old rotations are kept on disk; nothing is sent off-machine. - Loopback bind guard (
halton_meter/security.py). On startup, the daemon hard-exits withSystemExit(1)iflisten_hostorapi_hostresolves to a non-loopback address. Override withHALTON_METER_ALLOW_NON_LOOPBACK=1for container or VPN topologies that legitimately bind non-loopback — the override logs a warning on every start.
Changed
- Dependency upper bounds pinned in
pyproject.toml:mitmproxy>=10.0,<13,pydantic>=2.0,<3,httpx>=0.27,<1,click>=8.1,<9. Stops a future major release from breakingpipx upgrade/uv tool upgradeuntil the daemon is tested against it.
Removed
halton-meter reportrow cap.storage.read_recordslimitparameter is nowint | None = None(previously hard-coded to 10,000). Reports that scan the full local history no longer silently truncate.
v0.2.8 · 2026-05-24
Strict pause classification + a real `halton-meter cloud resume`. Transient errors no longer flip the daemon into a state that needs re-pairing.
Added
CloudForbiddenexception class. HTTP 403 on/v1/requests/batchis now classifiedpaused_reason="paused_forbidden". Recovery path: the workspace owner re-invites the machine, thenhalton-meter cloud resumeclears the pause — re-pairing (cloud connect) would revoke the still-valid existing key.cloud_sync_state.last_error_atcolumn (user_version6 → 7). Surfaced inhalton-meter cloud statusso operators can see how long ago the last transient error fired without grepping logs.
Changed
sync.paused_unauthorisedtriggers strictly on HTTP 401. All transient errors — 5xx,RemoteProtocolError,ReadTimeout,ConnectTimeout,httpx.ConnectError/ DNS failure, HTTP 429, HTTP 422/400 — route through a newerror_writerseam that never touchespaused_reason. Network blips no longer present as "your token was revoked"; only a real 401 does.halton-meter cloud resumeis a real recovery command, not a no-op. It reads the current pause reason; onpaused_manualit clears immediately. Onpaused_unauthorised/paused_forbiddenit hitsGET /v1/daemon/whoamionce with the stored token — if 200, it clears the pause, counters, andlast_error, unblocking the daemon without burning acloud connect.
v0.2.7 · 2026-05-23
Hotfix: auto-heal placeholder `base_url` values left over from spike scripts.
Fixed
- Auto-heal
[cloud].base_urlwhen it contains a placeholder TLD (.test,.example,.invalid,.local). The daemon rewrites the value tohttps://api.haltonmeter.comon next boot, logs the rewrite once, and proceeds. Catches stale config from staging / development scripts that would otherwise route every cloud request into a black hole.
v0.2.6 · 2026-05-23
Cloud onboarding loopback API. The browser-side onboarding shell at `app.haltonmeter.com` can now detect a running local daemon and pair it without copy/paste.
Added
GET /v1/cloud/state— returns{paired, version, hostname, port, ...}over loopback. Lets the dashboard's onboarding flow detect whether a local daemon is running and skip the install / start steps when it is.POST /v1/cloud/connect— triggers a pairing-code mint without the user copy/pasting from a terminal. The endpoint returns{code}; the browser pollsGET /v1/cloud/connect/statusfor approval, then completes pairing.- Chrome Private Network Access header
Access-Control-Allow-Private-Network: true. The cross-origin browser fetch fromhttps://app.haltonmeter.comto the daemon's loopback API now works without dev-mode flags on modern Chromium.
v0.2.5 · 2026-05-22
SaaS-launch release. Cursor cold-start attribution recovery, edge `unattributed` leak fix, friendlier transport-error UX, and the official backend URL is locked to `api.haltonmeter.com`.
Added
- Cursor cold-start lenient-slug scan (
attribution.layers.find_project_root_by_slug). Fires on Tier 4b / 4c registry miss; does a one-level scan of~/Documentsand~to warm theEdgeAttributionRegistryfor the originating process. Recovery telemetry:4b_lenient/4c_lenientinattribution.tier_hit. - Backfill v2 —
daemon/scripts/backfill_body_paths.pyrecovers Cursor-style rows by scanningfile://URIs and bare absolute paths in captured request bodies. Dry-run by default;--applygated.
Changed
- Friendly
cloud.transport_errorUX. The error message now surfaces the base URL and the config file path. Placeholder TLDs (.test,.example,.invalid,.local) raise immediately rather than burning the full retry budget; v0.2.7 layered auto-heal on top. - Official backend host locked.
cloud/constants.py:HALTON_METER_CLOUD_URL = "https://api.haltonmeter.com"is now the production default.HALTON_METER_CLOUD_URLenv var and--base-urlflag remain as dev / staging overrides. - CI matrix adds Python 3.14 (experimental,
continue-on-error: true) alongside 3.11 / 3.12 / 3.13.
Fixed
- Edge
unattributed/edge_storeleak closed.edge_attribution._resolve_via_psutilStep 8 callsattribution.resolver.resolve_unifiedas a final fallback after Steps 1–7.5 exhaust. Recovery telemetry:edge_attribution.resolver_fallback(INFO) emitted whenever the fallback wins.
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(readsCURSOR_WORKSPACE_LABEL/VSCODE_WORKSPACE_LABELfrom the originating process's environment) andide_argv_label(Electron-helper trailing-token regex against argv). Both corroborate against a per-edgeslug → abspathregistry so a stale or moved project cannot be impersonated. - Per-tier
attribution.tier_hittelemetry. Structured event on every tier match AND every strict-slug miss (4b_miss,4c_miss) — lets the operator grep~/.halton-meter/daemon.err.logto 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_unifiedentry point shared by the daemon's bypass path and the edge's pin path. Step 7.25 and its four helpers intagging.pyare deleted; the chromium-helper gate and IDE-family allow-list inedge_attribution.pyare 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.14to<3.15. Plainpipx install halton-meternow 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 onattribution_store.lookupis removed; the write-side prune loop is unchanged but now passesmax_age_s=86400.0(24h) so in-flight tunnels cannot have their rows evicted. - Attribution: daemon-side bounded parent-PID walk for the
claudefamily. When the originating process's cwd resolves toNoneor/(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_byteshelper — write to<name>.tmp, fsync,os.replaceover 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, defaultfalse),sync_interval_seconds(default60),max_body_bytes_per_upload(default524288— 512 KiB),per_project(dict of slug → bool). Bodies do not leave the machine untilbodies.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 SLUGdrops body uploads for one project while leaving the global switch on.halton-meter cloud privacy showrenders a "Body sync" section with master state, interval, byte cap, and per-project rules.BodyUploaderworker. Sibling to the metadata worker; readsrequest_bodies WHERE uploaded_at IS NULL, POSTs two envelopes per row (request + response) toPOST /v1/requests/{id}/body, stampsuploaded_aton success. Pause/last-error tracked independently incloud_body_sync_stateso a body 401 doesn't pause metadata sync.- Schema migration 5 → 6 adds
request_bodies.uploaded_at DATETIME NULLplus 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
cloudsubgroup matcheshalton-meter status. Rich panels and tables replace raw dicts;cloud statusadds a state banner (ACTIVE / DEGRADED / PAUSED / NOT-CONFIGURED) plus per-field health icons;cloud reconcileadds a daemon-vs-cloud variance line (zero = green check, <0.5% = yellow tolerance, otherwise red).--jsonflag oncloud statusstill 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_urlfrom TOML, find it empty, and report the daemon as un-paired. Now the connect flow writesbase_urlandenabled=truenext to the 0600 credentials write.halton-meter cloud reconcileno longer 422s. Daemon sentfrom/toquery params; cloud's reconciliation router expectsfrom_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 cloudCLI group.connect,disconnect,status,whoami,sync,reconcile,pause,resume. Pairing handshake (pairing/start→ user approves in dashboard →pairing/poll) mints a single-shothm_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.enabledso 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 isstandardwithsource_workdir = false— the high-leak field (local path) is off by default. Per-field global overrides; per-project rules (upload = falsemakes a project local-only). The daemon redacts before serialisation; the cloud cannot see what the daemon never sends. halton-meter cloud privacy show|set.showprints the resolved policy;set preset standard/set field.source_workdir false/set upload false --project acme-secretmutate the TOML in place (other sections preserved).- Observability + lifecycle. Daemon supervisor logs
cloud.supervisor.spawned/cloud.supervisor.skipwith structured reason.cloud_staterow tracks workspace name, machine id, hostname snapshot, last-connected-at, paused_reason. Worker retry envelope: exponential backoff capped at 300s for transport + 5xx + 429 (withRetry-Afterhonoured). 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 --appsbrings up twosystemd --userunits —halton-meter.service(daemon + mitm + FastAPI) andhalton-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 usessystemd --user+loginctl enable-linger+ certifi-bundle patching (system trust store untouched). - Linux operator reference at
docs/DAEMON_LINUX.mdin 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=1and anExecStop=halton-meter reset-proxybelt-and-braces so even a SIGKILL leaves the user's network state clean. halton-meter doctoredge-proxy row on Linux usessocket.connect_exfor 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_hostsis now derived as the inverse of the LLM allowlist, so the interceptor itself only attaches to traffic the daemon cares about. halton-meter doctorsurfaces the active intercept allowlist. One row, mono, exactly the hostnames the daemon will capture.
Fixed
- Freshness probe URL pointed at the private
haltonlabs/halton-meterrepo viaraw.githubusercontent.com, which returned 404 to unauthenticated clients —halton-meter doctorwas silently fail-open on every install. Probe now targetshttps://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-meterfollowed byhalton-meter initbrings up the three-process architecture: edge on127.0.0.1:8081, mitm listener on127.0.0.1:8090, loopback API on127.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 reportslices 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 (separatehalton-meter-dashboardrepo). 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.