Repository navigation
fix(browse): serve the dashboard from loopback and create the tables it reads - #112
Conversation
…it reads `han browse` opened https://dashboard.local.han.guru directly. Chrome 151 gates a public-origin page reaching a loopback port behind the local-network-access permission, which no response header can grant, so every request to the coordinator stalled with no error and the dashboard sat on "Connecting to Han Coordinator..." forever. `--local` was no fallback: it needs a source checkout, and the published package ships only bin/, schemas/ and README.md. The same bundle is now served from http://127.0.0.1, which puts the page and the coordinator in one address space so no permission is involved. This process fetches upstream itself, which is unrestricted. That exposed the next layer. Four dashboard panels then loaded forever on "no such table: daily_aggregates". The indexer writes daily_aggregates, hourly_aggregates and global_aggregates and the API reads them, but no migration ever created them, and every write was discarded with `let _` so nothing surfaced until a read failed. Those writes are now logged, and a migration creates the three tables. Backfill lives in han_indexer::backfill_aggregates_if_empty and runs in the background after the server is listening, alongside the existing deferred scan. A full rebuild costs about 45 seconds per 900k messages, which is long enough to blow the startup health budget if it ran inline. Backfilling then failed on "no such column: lines_added". Databases old enough to predate the SeaORM migrations were created by hand-written SQL and had m20260215_000001_initial recorded as applied without ever running, so their messages table lacks three columns the entity declares and every activity query sums. The migration adds them when absent. Verified against a real 8.8GB database: 52 daily rows, 24 hourly rows, 1036 sessions and 885,304 messages backfilled, all three columns added, and the dashboard renders with zero panels left loading. Guard tests were falsified by unregistering the migration, which reproduces the exact production error.
|
🚅 Deployed to the han-pr-112 environment in han-team-platform
3 services not affected by this PR
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
ReviewRead through the coordinator/migration changes, the loopback proxy, and the new tests. Solid diagnosis and the layered writeup in the PR description is genuinely helpful for anyone hitting this later. One correctness issue in the backfill path that I think undermines the fix for the exact scenario it's meant to solve; a couple of smaller things below it. CRITICALBackfill-vs-incremental-update race can silently skip the backfill, leaving history permanently incomplete.
This is exactly the upgrade scenario the PR targets (large pre-existing DB, restarting the coordinator), and it's plausible in normal use — e.g. upgrading while a Claude Code session is actively running and still appending to its JSONL file, so the watcher indexes new lines within the first second of startup. The 8.8GB verification run in the PR description apparently didn't hit it (probably because that DB had nothing left to index incrementally at the time), but that's scheduling luck, not a guarantee, and there's no test exercising the concurrent path — Suggested directions: make backfill's "has this run" signal independent of MODERATEDefault MINOR
NitThe 45s backfill estimate in the Everything else — the migration itself (idempotent |
Problem
han browsestill hung after #111. That PR fixed three coordinator defects but left the reported symptom in place, because the default surface is the one that cannot work.Three layers, each hidden behind the previous one.
1. The default origin cannot reach the coordinator
han browseopenedhttps://dashboard.local.han.guru. Chrome 151 gates a public-origin page reaching a loopback port behind thelocal-network-accesspermission. No response header grants it:Access-Control-Allow-Private-Networkis the superseded PNA mechanism.Same browser, same daemon, two origins:
http://127.0.0.1:4900RESOLVED 200 in 22mshttps://dashboard.local.han.guru--localwas not a fallback. It runs Vite from a source checkout, and the published package contains onlybin/,schemas/andREADME.md:So the same bundle is now served from
http://127.0.0.1, putting the page and the coordinator in one address space. This process fetches upstream itself, which is unrestricted. No assets ship and the release pipeline is untouched.2. Four panels then loaded forever
With the connection working, Activity, Code Changes, Model Usage and Time of Day spun indefinitely:
The indexer writes
daily_aggregates,hourly_aggregatesandglobal_aggregates; the API reads them for every unscoped dashboard query. No migration ever created them. Every write was discarded:which is why this shipped: the writes failed silently and only a read ever complained. Those three writes now log their failures, and a migration creates the tables.
Backfill is deliberately not in the migration. A full rebuild measured 22s for the daily pass alone on ~900k messages, and inline that would blow the coordinator's startup health budget, reintroducing the failure #111 just fixed.
han_indexer::backfill_aggregates_if_emptyruns in the background once the server is listening, beside the existing deferred scan, and is idempotent.3. Backfill then failed on a missing column
messageshad nolines_added,lines_removedorfiles_changed, though the entity declares all three and every activity query sums them.m20260215_000001_initialcreates them, and it is recorded as applied. But the table carries--comments SeaORM never emits:It was created by hand-written SQL from the TypeScript era and the migration was stamped without running. The migration now adds those columns when absent. Historical rows stay NULL, which reads as 0; the indexer fills them going forward.
Verification
Against the real 8.8GB database, not a fixture:
Dashboard, driven in Chrome 151 at
http://127.0.0.1:41956:WebSocket subscriptions confirmed over the same path:
tscclean; biome clean;cargo fmtclean; no clippy findings in the new filescontent-encodingnot double-encoded, upstream status preserved, unreachable upstream reports instead of hanging)no such table: daily_aggregatesNotes
han browseis now a long-running foreground process, as--localalready was, since it serves the page. It prints "Press Ctrl+C to stop" and shuts the listener down on SIGINT/SIGTERM.Code Changes will read zero until new sessions are indexed. Those columns never existed on old databases, so that history is genuinely unrecoverable rather than merely unaggregated.
Unchanged from #111 and still worth fixing separately:
install.shnever downloadshan-coordinator, and the release uploads all four Unix coordinators under one asset name. On this machine no coordinator binary existed at all, which is why indexed history stops at 2026-02-08.