Context
Running on Linux/Proxmox with the daemon dashboard exposed via Tailscale Funnel/Serve to other tailnet devices. Hit two bugs in the hand-managed dashboard-proxy.py (the network-exposed launcher referenced by dashboard-launcher-network.sh). Documenting here in case anyone else trips on them.
Plugin version: 5.5.0 (cache path: ~/.claude/plugins/cache/alexgreensh-token-optimizer/token-optimizer/5.5.0/).
Proxy file: ~/.claude/plugins/data/token-optimizer-alexgreensh-token-optimizer/data/dashboard-proxy.py.
Bug 1 — PUBLIC_HOST hardcoded to 127.0.0.1 contradicts the launcher's purpose
# dashboard-proxy.py header comment
"""Token Optimizer dashboard network proxy.
Binds 0.0.0.0:24842, spawns the upstream `measure.py dashboard --serve` ...
"""
# ...but at line 30:
PUBLIC_HOST = "127.0.0.1"
The dashboard-launcher-network.sh exists specifically so the daemon can serve the tailnet (vs the default localhost-only dashboard-launcher.sh). The systemd drop-in override is already wired to use it. But because PUBLIC_HOST = "127.0.0.1", the proxy still binds local-only and tailnet/LAN access fails with connection refused.
Repro: setup-daemon on Linux with the network drop-in active → curl http://<tailscale-ip>:24842/token-optimizer returns connection refused even though the proxy script is named "network".
Fix (one-liner): change to PUBLIC_HOST = "0.0.0.0".
Better fix: read from env var so users can choose 0.0.0.0 (default for the network launcher) or a specific bind address (e.g. 100.x.x.x to expose only on Tailscale, not LAN):
PUBLIC_HOST = os.environ.get("TOKEN_OPTIMIZER_DASHBOARD_HOST", "0.0.0.0")
Bug 2 — STARTUP_TIMEOUT = 20.0 is too short once trends DB grows
STARTUP_TIMEOUT = 20.0 # line 32
After accumulating ~520 sessions in the trends SQLite (and especially after a one-time jsonl-trim --apply sweep across all sessions), measure.py dashboard --serve --quiet cold-start takes right around 20s (HTML regeneration is proportional to trends/savings/sessions data). Result: proxy times out, upstream did not bind 127.0.0.1:24843 within 20.0s, daemon enters infinite restart loop, ports flap, dashboard unusable.
Repro:
- Run
jsonl-trim --apply on all session files (or just accumulate ~500+ sessions naturally)
systemctl --user restart token-optimizer-dashboard.service
- Watch
stderr.log show "upstream did not bind" repeatedly
- Standalone test:
time python3 measure.py dashboard --serve --host 127.0.0.1 --port 24850 --quiet → bind ~20s exactly
Fix (one-liner): bump to STARTUP_TIMEOUT = 90.0.
Better fix: env var override + maybe progress polling so it can wait longer when needed:
STARTUP_TIMEOUT = float(os.environ.get("TOKEN_OPTIMIZER_DASHBOARD_TIMEOUT", "90.0"))
Bonus context — daemon-token not auto-created
Slightly related: when the daemon is set up but no full session has yet exercised _get_or_create_daemon_token() (only invoked from setup-daemon and one other callsite), the file ~/.claude/plugins/data/token-optimizer-alexgreensh-token-optimizer/data/daemon-token doesn't exist. /api/token returns {"token":""}, all POST endpoints (/api/skill/archive, /api/v5/toggle, /api/mcp/disable) return 403 "Forbidden: invalid token", dashboard buttons silently fail. Minor — can be worked around by importing _get_or_create_daemon_token once. Worth ensuring daemon startup creates the token if missing.
Happy to PR the env-var version of (1) and (2) if useful.
Context
Running on Linux/Proxmox with the daemon dashboard exposed via Tailscale Funnel/Serve to other tailnet devices. Hit two bugs in the hand-managed
dashboard-proxy.py(the network-exposed launcher referenced bydashboard-launcher-network.sh). Documenting here in case anyone else trips on them.Plugin version: 5.5.0 (cache path:
~/.claude/plugins/cache/alexgreensh-token-optimizer/token-optimizer/5.5.0/).Proxy file:
~/.claude/plugins/data/token-optimizer-alexgreensh-token-optimizer/data/dashboard-proxy.py.Bug 1 —
PUBLIC_HOSThardcoded to 127.0.0.1 contradicts the launcher's purposeThe
dashboard-launcher-network.shexists specifically so the daemon can serve the tailnet (vs the default localhost-onlydashboard-launcher.sh). The systemd drop-in override is already wired to use it. But becausePUBLIC_HOST = "127.0.0.1", the proxy still binds local-only and tailnet/LAN access fails withconnection refused.Repro:
setup-daemonon Linux with the network drop-in active →curl http://<tailscale-ip>:24842/token-optimizerreturns connection refused even though the proxy script is named "network".Fix (one-liner): change to
PUBLIC_HOST = "0.0.0.0".Better fix: read from env var so users can choose
0.0.0.0(default for the network launcher) or a specific bind address (e.g.100.x.x.xto expose only on Tailscale, not LAN):Bug 2 —
STARTUP_TIMEOUT = 20.0is too short once trends DB growsAfter accumulating ~520 sessions in the trends SQLite (and especially after a one-time
jsonl-trim --applysweep across all sessions),measure.py dashboard --serve --quietcold-start takes right around 20s (HTML regeneration is proportional to trends/savings/sessions data). Result: proxy times out,upstream did not bind 127.0.0.1:24843 within 20.0s, daemon enters infinite restart loop, ports flap, dashboard unusable.Repro:
jsonl-trim --applyon all session files (or just accumulate ~500+ sessions naturally)systemctl --user restart token-optimizer-dashboard.servicestderr.logshow "upstream did not bind" repeatedlytime python3 measure.py dashboard --serve --host 127.0.0.1 --port 24850 --quiet→ bind ~20s exactlyFix (one-liner): bump to
STARTUP_TIMEOUT = 90.0.Better fix: env var override + maybe progress polling so it can wait longer when needed:
Bonus context —
daemon-tokennot auto-createdSlightly related: when the daemon is set up but no full session has yet exercised
_get_or_create_daemon_token()(only invoked fromsetup-daemonand one other callsite), the file~/.claude/plugins/data/token-optimizer-alexgreensh-token-optimizer/data/daemon-tokendoesn't exist./api/tokenreturns{"token":""}, all POST endpoints (/api/skill/archive,/api/v5/toggle,/api/mcp/disable) return 403 "Forbidden: invalid token", dashboard buttons silently fail. Minor — can be worked around by importing_get_or_create_daemon_tokenonce. Worth ensuring daemon startup creates the token if missing.Happy to PR the env-var version of (1) and (2) if useful.