refactor: ttyd reaper, argv daemon launch, url dedup, keep-alive, retries (#101 P2) - #104
Conversation
🔍 Local review (cycle 1)Reviewed locally (Claude subagent + Codex companion), no bots pinged. Both reviewers ran; findings triaged against the real code +
Both findings stem from the
Verified correct (both reviewers): reaper thread (no double-clean race — removal happens under lock; daemon thread catches exceptions; Applying the optional C2 truncate hardening in the cleanup pass; merge is left to the maintainer. |
Non-blocking finding from the local review of #104: the argv refactor changed the daemon launches from `| tee "$LOG"` (which truncates the log on open) to `>>"$LOG"` (append). Without a matching truncate, server.log / droid /ao logs on the persistent /home/hapi volume would keep stale content across restarts — for server.log that means a brief window where the /home/hapi/url fallback pairs a fresh token with an old relay URL (the live dashboard builder self-heals, but the fallback file can go stale) — and grow unbounded. Replace `touch "$LOG"` with `: > "$LOG"` (truncate-or-create) for the three daemon logs, restoring the old tee truncate-at-startup semantics. Update the server-log pre-create test accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgvJhzd8gEhXVE4MVu18nn
|
Applied the C2 cleanup in 1ad695c: the three daemon logs (server/droid/ao) are now |
…ries (P2) Six P2 refactoring-debt fixes from the #101 code review, plus NOTE comments for the deferred TOCTOU finding (#8). processes were only reaped lazily inside list_terminals()/get_terminal(), so with no list/get/health traffic a defunct zombie pinned its PID slot forever. The reaper polls periodically (start/stop wired into main + signal handler). of the string-interpolating run_as_hapi (`sh -c "... ${command}"`) — a latent root-context command-injection surface. `| tee` is replaced with a redirect (argv can't express a pipe, matching the tunnel helpers) and inline env vars go through `env NAME=VAL`. The unused string run_as_hapi is removed. ttydproxy.views.build_hapi_url_from_runtime the dashboard uses, replacing a drifted bash reimplementation (bash took the FIRST relay URL + left the token un-encoded; views takes the LAST + quote()s both). skip-set, and read resp.getheaders() once (was called twice per response). A keep-alive connection pool was intentionally NOT added — a shared HTTPConnection across ThreadingHTTPServer worker threads would introduce a concurrency bug. tab_fix_script and delegate to them from the parent page + virtual keyboard, removing byte-identical containsFiles copies and three hard-coded socket- discovery + '0' ttyd-prefix sites (protocol change now lives in one place). ao git-fetch + Hermes git-clone/pip network steps in the CLAUDE.md retry loop (`for i in 1 2 3 4 5; ... && break || sleep 10`), previously applied only to npm. (narrow window, not a security boundary) and point at a per-terminal lease as the proper fix, tracked separately. No new env vars. No port or volume changes. Closes #101 findings #7, #9, #12, #13, #14, #15 (documents deferred #8). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgvJhzd8gEhXVE4MVu18nn
Non-blocking finding from the local review of #104: the argv refactor changed the daemon launches from `| tee "$LOG"` (which truncates the log on open) to `>>"$LOG"` (append). Without a matching truncate, server.log / droid /ao logs on the persistent /home/hapi volume would keep stale content across restarts — for server.log that means a brief window where the /home/hapi/url fallback pairs a fresh token with an old relay URL (the live dashboard builder self-heals, but the fallback file can go stale) — and grow unbounded. Replace `touch "$LOG"` with `: > "$LOG"` (truncate-or-create) for the three daemon logs, restoring the old tee truncate-at-startup semantics. Update the server-log pre-create test accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgvJhzd8gEhXVE4MVu18nn
1ad695c to
7c97c65
Compare
|
Rebased onto merged Conflicts (2, both resolved by union — kept the P1 change from main AND layered the P2 change on top, nothing reverted):
Auto-merged cleanly (verified, not silently mismerged):
Verification (all green): Not merging — leaving that to the maintainer once CI shows green. |
Summary
Six P2 refactoring-debt fixes from the multi-agent code review in #101, plus NOTE comments for the deferred TOCTOU finding (#8).
manager.py,app.py). Added a background daemon reaper thread; zombies were only reaped lazily insidelist_terminals()/get_terminal(), so with no list/get/health traffic a defunct process pinned its PID slot forever. Start/stop wired intomain()+ the signal handler.run_as_hapiinterpolated args intosh -c(entrypoint.sh). Routed droid/ao/hapi startup through the argv-saferun_as_hapi_argv(a latent root-context command-injection surface).| tee→ redirect (argv can't express a pipe, matching the tunnel helpers), inline env viaenv NAME=VAL; the unused stringrun_as_hapiis removed.entrypoint.sh). Delegated the legacy/home/hapi/urlwriter to the samettydproxy.views.build_hapi_url_from_runtimethe dashboard uses (bash took the FIRST relay URL + left the token un-encoded; views takes the LAST +quote()s both).proxy.py).HOP_BY_HOP_HEADERSis now afrozenset, the HTML skip-set is precomputed, andresp.getheaders()is read once (was twice per response). A keep-alive connection pool was intentionally not added — a sharedHTTPConnectionacrossThreadingHTTPServerworkers would introduce a concurrency bug.app/assets/*.html). Exportedwindow.__containsFiles/window.__sendToTTYDfrom the injectedtab_fix_scriptand delegated to them from the parent page + virtual keyboard, removing byte-identicalcontainsFilescopies and three hard-coded socket-discovery +'0'ttyd-prefix sites.Dockerfile). Wrapped the Node/ttyd/cloudflared/chisel curl downloads and the ao git-fetch + Hermes git-clone/pip steps in the CLAUDE.md retry loop, previously applied only to npm.#101 findings closed
run_as_hapish -c injection) — security, PLAUSIBLEDeferred: #8 (port-reuse TOCTOU) — narrow window (usually
ECONNREFUSED→502), not a security boundary. NOTE comments added inapp.py+manager.pypointing at a per-terminal lease/refcount as the proper fix, tracked separately.New/changed env vars
None.
Ports / volumes
No changes.
Tests
New: reaper tests in
test_ttyd_manager.py,TestResponseHeaderEfficiencyintest_proxy_http.py,TestDockerNetworkRetries+ a droid-daemon injection test + relay-URL delegation tests intest_shell_scripts.py,__sendToTTYDdelegation in the vkbd JS tests. Fullpytest tests/andnpm testgreen;bash -n entrypoint.shclean.🤖 Generated with Claude Code
https://claude.ai/code/session_01KgvJhzd8gEhXVE4MVu18nn