Skip to content

feat(v0.6.1): heartbeat-stream /query/ + /upload/ (survive mobile/Tailscale tunnel drops)#1073

Merged
Hashevolution merged 1 commit into
mainfrom
feat/v0.6.1-heartbeat-streaming-long-endpoints
Jun 26, 2026
Merged

feat(v0.6.1): heartbeat-stream /query/ + /upload/ (survive mobile/Tailscale tunnel drops)#1073
Hashevolution merged 1 commit into
mainfrom
feat/v0.6.1-heartbeat-streaming-long-endpoints

Conversation

@Hashevolution

Copy link
Copy Markdown
Owner

Summary

Operator (phone over Tailscale): a long query returned "Failed to fetch" before the answer, and the composer upload chip stayed stuck even though the server finished (entities formed). Shared root cause: a request that holds an idle connection for 30–90s (full RAG query / image ingest with vision OCR) is evicted by the mobile network or a Tailscale re-handshake. The blocking call inside the async handler also pinned the event loop, so /trace/poll couldn't stream reasoning stages live.

Fix: core/http_heartbeat.stream_json_with_heartbeat runs the blocking work in a worker thread (frees the loop β†’ /trace/poll streams stages live) and emits JSON-insignificant whitespace every ~12s while it runs, so bytes keep flowing and the tunnel stays up. The heartbeat is leading whitespace, which r.json() / JSON.parse ignore β†’ no client change. current_trace_id (ContextVar) propagates into the thread via anyio.to_thread, preserving trace correlation.

Bonus

Threading the blocking pipeline also fixes the "reasoning not reflecting live" symptom β€” the event loop is now free to serve /trace/poll during the request.

Verification

  • tests/test_http_heartbeat.py 4/4: stream+parse, error-in-body, fast-path, contextvar propagation.
  • routes.query / routes.auth import OK; slow blocks wrapped, validation (400/413/403) still raises proper status before streaming.
  • test_measurement_critical_surfaces 33/33.
  • Live end-to-end pending on the operator's server (real query/upload over the tunnel) β€” the helper + propagation are unit-tested; the route wraps are structural.

Trade-off

A rare slow-path error (after streaming starts, status already 200) now surfaces in the body ({"answer":"","error":...}) instead of a 5xx; the file is still saved + marked failed server-side. Validation errors are unaffected (raised before streaming).

Quality delta

Quality delta: exempt (label: feat) β€” transport-robustness wrapper on two routes + a new core/http_heartbeat util; core/retrieval + core/graph traversal + core/reasoning 0 lines.

πŸ€– Generated with Claude Code

…ailscale tunnel drops)

Operator (phone over Tailscale): a long query returned "μ„œλ²„ μ—°κ²° μ‹€νŒ¨:
Failed to fetch" before the answer, and the composer upload chip stayed
stuck even though the server finished (entities formed). Both share one
root cause: a request that holds an IDLE connection for 30–90s (full RAG
query / image ingest with vision OCR) is evicted by the mobile network or
a Tailscale re-handshake. The blocking call inside the async handler also
pinned the event loop, so /trace/poll couldn't stream reasoning stages
live.

Fix: `core/http_heartbeat.stream_json_with_heartbeat` runs the blocking
work in a worker thread (frees the loop β†’ /trace/poll streams stages live)
and emits JSON-insignificant whitespace every ~12s while it runs, so bytes
keep flowing and the tunnel stays up. The heartbeat is leading whitespace,
which r.json()/JSON.parse ignore β†’ NO client change. current_trace_id
(ContextVar) propagates into the thread via anyio.to_thread, preserving
trace correlation (unit-tested).

- core/http_heartbeat.py: the helper (3.3KB)
- routes/query.py: wrap the rag_engine.query pipeline in _work + stream
- routes/auth.py: wrap the /upload/ ingest slow-block in _work + stream
  (validation/size/auth errors still raise proper status before streaming;
   a rare slow-path error now surfaces in the 200 body, file marked failed)
- tests/test_http_heartbeat.py: 4 tests (stream+parse / error / fast /
  contextvar propagation)

core/retrieval + core/graph traversal + core/reasoning 0 lines (new util
is core/http_heartbeat). Live end-to-end verify pending on the operator's
server (helper + propagation unit-tested; routes import-OK + structurally
wrapped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B7gaaBkpM3xYTzxhYrGHnR
@Hashevolution
Hashevolution merged commit 8014c03 into main Jun 26, 2026
2 of 4 checks passed
@Hashevolution
Hashevolution deleted the feat/v0.6.1-heartbeat-streaming-long-endpoints branch June 26, 2026 01:54
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant