Repository navigation
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
- Run the server (official nightly, orchestration V2) against a moderately large
statev2.sqlitewith many threads. - Cold-open a client with many threads mounted and a cold snapshot cache (fresh profile), so every mounted thread misses its snapshot cache.
- Each mounted thread fires
GET /api/orchestration/threads/:threadId/boundedwith the client's fixed 6 s budget (DEFAULT_BOUNDED_THREAD_SNAPSHOT_TIMEOUT_MS = 6_000,packages/client-runtime/src/state/boundedThreadSnapshotHttp.ts:19), with no cross-thread concurrency bound (threads.tsonly has a per-threadapplyLock,packages/client-runtime/src/state/threads.ts:235). - The server serializes all of them on the single-permit SQLite client (
semaphore = yield* Semaphore.make(1),packages/shared/src/nodeSqliteClient.ts:262) underBEGIN IMMEDIATE, each holding the lane across a multi-table projection read insidesql.withTransaction(getThreadSnapshotWindow,apps/server/src/orchestration-v2/ProjectionStore.ts:4856). - The snapshots queue, all exceed 6 s, and every client request fails with
RemoteEnvironmentAuthTimeoutError→unavailable→ socket full-snapshot fallback, while the server-side fibers keep running until the client cancels them. cloudflared logs both sides asIncoming request ended abruptly: context canceled.
Expected behavior
Snapshot HTTP handlers bound their work server-side (timeout with a retryable busy signal the client can back off on instead of hanging until cancel), the client limits in-flight snapshot loads, and downstream-client-goes-away relay lines log at debug rather than warning.
Actual behavior
- The three thread-snapshot handlers (
threadSnapshot,threadBoundedSnapshot,threadHistoryPageinapps/server/src/orchestration-v2/http.ts) have no server-side timeout/deadline — verified by reading the handlers, theThreadManagementService→Orchestrator→ProjectionStoredelegation chain, andtraceLocalHandlerWork(tracing only). A slow read holds its SQLite transaction open until the client gives up and cancels. - The client fans out one 6 s bounded request per mounted cache-miss thread with no semaphore, staggering, or batching, so a shell restore is a thundering herd against the single SQLite lane; mass timeouts then stampede onto the socket full-snapshot path the bounded route exists to avoid.
classifyRelayClientOutput(apps/server/src/cloud/ManagedEndpointRuntime.ts:81) maps the resulting... ERR Incoming request ended abruptly: context canceled ...lines towarningvia the genericERRrule, with no special case (repo-wide grep for the substring: zero hits). Each line becomes aRelay client reported a transport warninglog; nothing actionable —warningon this path only logs (observeConnectorOutput, no restart/recovery). In reporter logs this is on the order of ~135 such warnings/day plus repeated enrichment warns for deleted checkouts on wake.
Impact
Major degradation or frequent failure
Version or commit
Local checkout 0678e4e23d (2026-10-07); re-verified each claim against upstream main tip via the contents API on 2026-10-10 (handler file unchanged — no timeout combinator; classifier unchanged; client still 6 s default with no semaphore).
Environment
Linux server, official nightly builds (no fork). Client: web.
Logs or stack traces
Representative relay lines (from cloudflared output observed in reporter logs):
ERR Incoming request ended abruptly: context canceled ...
Server side, each corresponds to a snapshot handler fiber dying on Interrupt after the client's 6 s cancel; client side, RemoteEnvironmentAuthTimeoutError → unavailable → socket fallback.
Workaround
None on official nightlies — snapshot timeouts/knobs are not exposed via config or environment (no *_TIMEOUT/snapshot_* env knobs found in packages/ or apps/), and the relay log level for the spawned cloudflared process is fixed at --loglevel info (ManagedEndpointRuntime.ts:350).
Suggested fix shape (for triage)
- Bound the three snapshot handlers server-side (e.g.
Effect.timeoutOption≈ 5 s, inside the client's 6 s budget) and answer a retryable busy signal (HTTP 429) instead of hanging until cancel. - Bound in-flight client snapshot loads (e.g. a small semaphore) and retry once on busy rather than falling straight back to the socket full snapshot.
- Special-case
Incoming request ended abruptly: context canceledtodebuginclassifyRelayClientOutput— it means the downstream client went away, not a tunnel fault.
Related
- [Bug]: Server: the thread list read blocks the event loop and every other database call for seconds on large databases #14701 covers the sibling shell-snapshot stall (event-loop blocking on large DBs); this issue is the thread-snapshot timeout/herd/log-noise chain. The two compound: any slow snapshot burns the single lane for everyone behind it.
- Already landed upstream (not duplicates): bounded snapshots (feat(orchestrator): introduce new orchestrator #2829), FTL/PNC relay classification (fix(server): surface cloudflared FTL/PNC relay logs as warnings, not debug #5076), tunnel recovery (fix(server): recover a deleted tunnel when Cloudflare says "Tunnel not found" #16648).
Activity
josephv123 commentedon Oct 15, 2026
Consolidated independent reproduction of the shared-writer part of this issue. Verified source at
e536bfd33bc3c351997141d93790df75eeded7b9; the relevant projection/SQLite files are unchanged from the original audit atec1313c8f8333292506f05a355bb72d308a455d8.A tiny synthetic WAL database with one thread and one completed tool item is enough: a worker connection takes
BEGIN IMMEDIATE, signals that it holds the lock, and releases it after 3,000 ms. The main process then schedules a 1 ms heartbeat and calls the productiongetThreadSnapshotWindowmethod. The control wraps that exact method in T3's existingNodeSqliteClient.readOnlyhelper. No provider, relay, real history or large fixture is needed.Retained Node 24.21.0, Linux, alternating two rounds:
Re-running the same portable synthetic probe during consolidation under Node 26.10.0 gave 3,047.3 ms versus 1.33 ms heartbeat delay. Both returned the same one-item snapshot. The first reader use includes worker startup.
The causal path is:
getThreadSnapshotWindowusessql.withTransactionwithoutreadOnly.NodeSqliteClientroutes unmarked work to the synchronous writer, whose transaction usesBEGIN IMMEDIATE.Sqlite.tssets a 5,000 ms busy timeout. The native synchronous wait prevents an Effect timeout or unrelated JS callback from running until it returns.Minimal experimental sequence for a checkout-level regression test:
The complete checkout probe is included below. The worker hold is an experimental input, not a measured production lock frequency. No multi-second stall appeared in the retained production trace windows, so this is a controlled root-cause reproduction, not a claim that every slow cold-open has this cause. No live database was modified.
Complete checkout-level reproduction (temporary synthetic database only)
Install this checkout's normal development dependencies, save the following as
snapshot-contention.mjs, and run from the repository root with Node 24:The script imports production services from the current working directory. It uses a worker to release the experimental write lock independently of the blocked main loop and removes its synthetic fixture on completion. Do not substitute a live database path for
synthetic.juliusmarminge commentedon Oct 21, 2026
Tested current main 70e5e384da on Linux, Node 24.21.0, Chrome web via
vp run dev, using isolated state. An existing populated stopped thread opened successfully. A larger synthetic projection fixture (100 stopped threads × 300 completed command items, 3 KB output/item; 125 MB SQLite file) also opened successfully in a fresh incognito profile. The sidebar contained 100 fixture rows, but opening it requested the selected conversation's snapshot, rather than a snapshot for every sidebar row.A browser-issued burst of 100 distinct bounded-snapshot HTTP requests with 6,000 ms abort deadlines returned 100 HTTP 200 responses in 145–1,513 ms. Opening a populated fixture through the UI returned HTTP 200 in 23 ms server-side:
The controlled writer-lock probe in the existing comment is separate evidence; these results do not disprove contention under that condition. The cloudflared/relay path was not verified.
Please provide the current affected build, approximate database size/thread/item counts, the exact UI steps that mount multiple conversations during a cold open, and sanitized request timings showing the bounded requests timing out and falling back. Those details would distinguish the cold-open trigger from the independently demonstrated injected-lock stall. No database contents or credentials are needed.
torztomasz commentedon Nov 1, 2026
🤖 Generated by an LLM on behalf of Tomasz Tórz.
Data for the questions above.
Production host: VPS, 8 vCPU,
statev2.sqlite6.06 GiB (0 KiB reclaimable pert3 doctor --dry-run), agents running. Its own traces:http.orchestration.loadThreadSnapshotWindowp50 235 ms, p95 2.4 s, max 4.4 s over 30 min. The slow ones are onesql.transactionwith a 675 ms wait before the first read and 20 sequential queries running ~10x slower than in a fast request: CPU contention with the agents, not a query plan. The same queries on an idle machine with a copy of the same data: 29–81 ms for the largest threads (1,000–2,000 items).Client effect (iOS simulator, same data copy, weak 5G 250 ms / 2 Mbps, server CPU at 10 %, host answering the bounded snapshot after 6.5 s), touch → first feed for never-opened threads:
After the timeout, the socket fallback downloads the thread again, uncompressed on iOS, over the same slow link: cheap for small threads, very expensive for big ones. A longer client budget removes the tail but makes small threads wait for the slow HTTP answer; the server deadline proposed above would avoid both. I have the client change and a harness (sanitized DB copy, shaped Wi-Fi/5G, CPU-throttled host) if you want either.
Alb11747 commentedon Nov 2, 2026
Additional user report (2026-11-01): loading earlier activity on mobile through the managed relay still fails after more than five reloads.
This is the older-history paging path, rather than just initial bounded-snapshot loading. The user supplied a screenshot showing:
GET https://<managed-relay-host>/api/orchestration/threads/<thread-id>/history?cursor=<redacted>&deferWork=1&compactIds=1.timed out after 6000ms.Expected: earlier activity loads, or the client provides useful recovery when the history request fails. Actual: older activity remains inaccessible through repeated reloads, and the raw endpoint URL and very long encoded cursor occupy much of the mobile viewport.
Source inspection at fork checkout
fd5c10196a08458c3b876e235f8d48836fe1ce20:packages/client-runtime/src/state/threadHistoryHttp.tssetsDEFAULT_THREAD_HISTORY_TIMEOUT_MS = 6_000and suppliesdeferWork=1andcompactIds=1for ordinary history paging, matching the screenshot.packages/client-runtime/src/rpc/http.tsapplies the request deadline and includes the entire request URL inRemoteEnvironmentAuthTimeoutError.message.packages/client-runtime/src/state/threads.tspasses that message into history error state throughformatHistoryError; a failed HTTP history page returns an error rather than taking the bounded-snapshot loader's socket fallback path.Read-only inspection of the installed Windows desktop server's rotated
server.trace.ndjson.1also found history requests for the exact thread ID shown in the screenshot. One correlated span chain is:The interruption stack includes
ProjectionStore.readTimelineIndex. Other history service spans in that same rotated log were interrupted after 20345.4362 ms, 22166.5878 ms and 18557.0027 ms; those additional spans have not individually been correlated to the screenshot's thread. A later history service span succeeded in 3513.874 ms. These are observed server span durations, not a benchmark or proof that any specific mobile reload generated a particular span. The common trace ID is not useful for request correlation here; the chain above was correlated by parent/span IDs, and the endpoint span's path supplies the thread ID.Investigation update
The installed desktop/server version is
0.1.7-nightly.20261101.4016.fork.1, established from the local server-runtime record and desktop version marker. The mobile client version remains unknown. The history-loading implementation was verified directly in the installed server bundle, rather than assumed to match the checkout.Confirmed expensive path:
getThreadHistoryPageinvokesProjectionStore.readTimelineIndexbefore applying the cursor/page selection. That reconstructs the entire thread timeline, loads its runs and attempts, and queries every local activity row in ordinal order. WithdeferWork=1, the query also joins nodes and attempts, performs a correlated latest-attempt lookup, and scans dynamic-tool payload text withLIKEchecks to classify persistent output. The affected thread has approximately 38,000 activity rows. The SQL has no page limit; the cursor bounds the later selection, not this preparatory read. Fork ancestry can add recursive timeline reads, though that has not been established as a factor for this thread.The read-only query plan uses the thread/ordinal index and indexed join lookups. This is not evidence of a missing basic thread index or a whole-database table scan; the problem is the amount of full-thread work performed per page.
Request correlation was extended using parent/span IDs. A matching-thread request lasted 22166.7921 ms before interruption, with a reader transaction lasting 22166.2371 ms and reported reader acquisition wait of 0.006 ms. Within it,
readTimelineIndexwas interrupted after 5036.6243 ms: its records read took 1099.4783 ms and its timeline SQL span took 3936.8103 ms before interruption. The full transaction duration includes cancellation/cleanup; it must not be described as the timeline SELECT's execution time. The matching-thread request that succeeded in 3514.0579 ms spent 3300.1203 ms inreadTimelineIndex, including 3004.5822 ms in the timeline SQL span, while page materialization took 127.7366 ms.Database load matters too. During the slow matching-thread request, other overlapping database spans were also slow, including shell/projection reads and simple node/policy lookups. A standalone read-only execution of the timeline SELECT, using local Node v24.21.0 / SQLite 3.53.4, returned approximately 38,000 rows in about one second. That diagnostic is not the installed server runtime or an end-to-end history-handler reproduction. It establishes that the query is not inevitably slower than the client deadline in isolation; the precise reason for its larger latency inside the busy server remains unresolved. CPU, disk, worker scheduling and transfer overhead have not been isolated, and these traces do not directly measure time executing SQLite's VM separately from worker response time.
Cancellation can retain work: in
packages/shared/src/nodeSqliteClient.ts, the reader worker executes synchronousstatement.all(...)and posts the result back. The request callback has no cancellation action that interrupts that running SQLite statement. Cancelling the caller therefore does not itself stop an in-progress statement; transaction rollback/cleanup can wait behind it. This is a source-supported mechanism consistent with the much longer outer transaction lifetimes, not proof that each reload created a particular amount of extra load.Keeping this update on #17765 because it explicitly includes
threadHistoryPageand the six-second timeout/cancellation chain. This reproduction is older-history paging on a Windows desktop server with a mobile relay client, rather than the original Linux/web cold-open herd. The original report's single-reader explanation should not be assumed to describe this newer build; the inspected checkout uses a four-reader pool. #24895 is relevant background on costly history reads, but no identical root query has been established between the reports.The supported diagnosis is full-thread history preparation exceeding the fixed client budget under server/database load, with running SQLite work surviving caller cancellation. A durable fix should investigate bounding the pre-page work and cancellation/recovery together. Raising the timeout alone does not address that work. No fix or deterministic end-to-end reproduction is claimed.
The screenshot contains private conversation content, so this report transcribes the error with relay host, thread ID, and cursor redacted instead of publishing the original image.
Reported on the user's behalf by GPT-6.1 Sol (high reasoning), through Codex in T3 Code.
Alb11747 commentedon Nov 2, 2026
Synthetic history follow-up — 2026-11-02
Follow-up to #17765 (comment). These experiments concern older-history paging, not the bounded-snapshot socket-fallback path. Related background: #24895.
The original failure did not reproduce at the supplied aggregate size. A larger fixture did cross the six-second boundary under four concurrent history requests, then recovered on the next identical burst. Persistent failure across five or more reloads was not reproduced.
The result supports history-wide preparation as an expensive path, and demonstrates a possible deadline crossing with sufficiently large synthetic history. It does not identify the cause of the extra in-server slowdown observed on Windows, or establish that the managed relay caused it.
Environment and isolation
main,96f3bdddc43074bcb2f5d02d1a9b17020c09371f. No application changes, Git integration, PR, or deployment. This comment publishes the diagnostic results and scripts only.5.15.0-198-generic; Intel Core i5-11600K, 6 physical/12 logical host CPUs. Container CPU quota800000/100000(8 CPUs), memory limit 12 GiB. Host resources are shared. Observed load averages varied around 7–9; no caches or resource limits were changed. No container OOM events occurred. Timing variance must not be interpreted as controlled hardware performance.v24.22.1, built-in SQLite3.53.4, Effect4.0.5. This is source execution at the pinned revision, not a measurement of the installed Lazarus binary or of the Windows desktop bundle.source/;HISTORY_BENCH_DEPENDENCIEScan instead select an existing matching dependency installation.Fixture and path exercised
Seed
20261102. The baseline has exactly 38,000 activities, 4,900 runs, 4,900 attempts, 30,000 nodes, and 136,099,170 bytes of activity JSON (136 MB decimal, 129.8 MiB). Its activity counts are 9,800 commands, 6,700 dynamic tools, 5,600 assistant messages, 4,100 checkpoints, 3,500 reasoning items, 3,000 user messages, 2,000 notifications, 1,800 subagents, 1,400 file changes, and 100 system notices. Text is deterministic invented content, interleaved with a seeded shuffle. Tool text reaches 200,000 bytes; file diffs reach 120,000 bytes.The other fixtures multiply all counts by two/four. Two-times activity payload is about 272 MB; four-times is 545,162,396 bytes. In the original retained scale2/scale4 metadata the
countshistogram records base counts; multiply it byscale. The final reusable generator explicitly records both base and scaled counts.Real migrations 1–76 created WAL databases. All persisted payloads were checked against the real AppThread/Run/RunAttempt/ExecutionNode/TurnItem JSON schemas.
PRAGMA integrity_checkreturnedokafter each seed.Requests go through the real
OrchestrationHttp.layerand HttpApi schema/serialization, withdeferWork=1&compactIds=1, a real encoded/decoded cursor, and a synthetic authenticated principal carrying the read scope. A narrow synthetic ThreadManagementService port forwards all five arguments to the real ProjectionStore. This bypasses orchestration startup and transcript import, not ProjectionStore's realgetThreadHistoryPage, four-reader pool, read-only transaction acquisition, SQL, worker, page selection, materialization, deferred grouping, wire projection, or compact-ID response codec. Valid successful pages contain 282/350/294 rows at baseline/2×/4× and retainhasMoreHistory=true.The six-second boundary uses AbortController against the in-process web handler. It models client abandonment; it does not invoke mobile's authenticated HTTP client or traverse a network/relay. Measurements include response body reading and successful response validation. Final baseline output additionally separates handler/body from client decode.
Results
The complete request matrix is below, generated from retained JSON results. The scripts recreate detailed worker/Effect timings and standalone query plans. No claim is made that the first process request is disk-cold: fixture creation, migrations, prior runs, and shared host activity influence cache state. “Quiet” means no other benchmark workload, not an idle host.
Final baseline rerun: warm serial requests 0.445–0.549 s, four concurrent requests 1.025–1.386 s, all successful. Earlier baseline runs also succeeded, up to 1.170 s serial. At 2×, quiet requests took 1.111–1.361 s and four concurrent requests 3.053–3.200 s, all successful.
At 4× (152,000 activities, 19,600 runs/attempts, 120,000 nodes), quiet requests took 3.307–3.877 s; paired loads took 4.604–4.942 s. The first four-request burst aborted all four calls: timers fired at 6.068–6.086 s and handler/body settlement followed after another 13.8–16.7 ms. These are actual six-second deadline crossings, with small timer scheduling delay. The in-process HTTP adapter returned its local cancellation status 499; this is not a remote server status observed by mobile. The next identical four-request burst succeeded in 5.430–5.477 s. Thus the benchmark does not demonstrate an accumulating reload/cancellation loop.
Writer comparison correction: the earlier scale2/scale4 runs issued twenty UPDATE statements with the existing timestamp. These are update attempts, not evidence of physical write contention. The final baseline harness was corrected to change both the column and its valid JSON timestamp on every update, and the baseline matrix was rerun successfully. Only that final baseline phase establishes actual concurrent synthetic metadata changes; no throughput or contention conclusion is drawn from it.
Timing boundaries
Baseline warm serial spans: canonical records (runs/attempts and thread) 101–138 ms; full timeline reconstruction 398–492 ms; timeline worker SQLite execution 197–251 ms; worker roundtrip 261–328 ms. Non-empty page materialization calls were under 15 ms. Reader permit waits were 0.016–0.026 ms. These stages nest: do not add them as independent costs.
At 4×/four requests, canonical reads were 1.507–1.821 s and timeline reconstruction 3.964–5.649 s across the two bursts. The first burst's timeline SELECTs spent roughly 2.68–2.74 s executing inside the worker, with roundtrips about 3.10–3.95 s. The reader permit waits across both bursts were only 0.007–0.028 ms. The large main-thread/worker/SQLite differences are observed timings, not proof of CPU, disk, transfer, or scheduler causation. A roundtrip includes worker dispatch, preparation, execution, result serialization/transfer and main-thread scheduling. The execution hook measures synchronous
statement.allplus its tiny diagnostic start message; it excludes statement preparation.Standalone comparisons use the exact captured timeline SELECT with its two bound parameters on each synthetic database, without the Effect service or worker transfer. Repeated baseline samples were 0.541/0.251/0.269 s; 2× 1.122/0.486/0.423 s; 4× 1.182/0.827/0.897 s. They were run sequentially after the corresponding service experiments with no cache clearing. They are not matched simultaneous controls and do not establish why in-service execution was slower. Query plans retain the thread/ordinal index, node/attempt primary-key lookups and indexed correlated latest-attempt lookup; they do not scan the entire database.
Cancellation, separately tested
The milestone probe aborts after the worker announces execution of the timeline SELECT. In the final baseline run, abort occurred about 102 ms after request start; the worker still returned all 38,000 metadata rows. Its
statement.allduration was 204 ms and roundtrip 282 ms. HTTP cleanup settled about 284 ms after abort, with the outer request lasting about 386 ms. At 4×, the worker returned all 152,000 rows; execution was 1.036 s, roundtrip 1.339 s, and cleanup about 1.340 s after abort. The next request succeeded at every size.This directly confirms that caller cancellation did not interrupt the synchronous in-flight SQLite statement. The interrupted timeline Effect span ends before the worker reply; the enclosing transaction/handler includes cleanup waiting. Never label that outer duration “pure SELECT time.” In the actual six-second first burst, cleanup was short because the expensive timeline SELECTs had already returned. Do not conflate that burst with the separate mid-SELECT probe.
Limits and next experiment
All scripts and workers exited. The repository remained clean at the starting revision. No repo-wide checks were run: validation was fixture schema/integrity checks, real HTTP success-codec validation, timing/row-count checks, query plans, abort/recovery behavior, and a final rerun of the reusable harness at baseline.
Source references
The measured revision is a public personal-fork commit, not upstream main:
The timeline plan in all three synthetic databases was:
Complete measured request matrix
Seconds; excludes the explicit mid-query cancellation probe.
Runnable scripts
Save the named files below together in a new external work directory. Linux and Node 24.22.1 were tested. The cgroup reads are Linux/container diagnostics; this published version is not a portable Windows harness. No database download is needed: every fixture is generated from invented data using real migrations and payload schemas.
Use a separate source checkout pinned to the measured commit, with its normal locked development dependencies installed:
Alternatively, export
HISTORY_BENCH_DEPENDENCIES=/path/to/an/already-installed-matching-checkoutbefore linking/running. The recorded run used Effect and @effect/platform-node 4.0.5. Do not point any script at live user state.HISTORY_BENCH_OUTmust be a child of this external script directory, outsidesource/; the database filename is alwayssynthetic.sqlite. Existing fixtures require matching seed/scale/revision metadata. Choose a new child directory to regenerate instead of replacing another database.The initial baseline database is approximately 191 MiB; all three databases together are approximately 1.3 GiB, excluding transient WAL/temporary space and checkout dependencies. Seed/integrity work can briefly be synchronous. These are bounded diagnostics for a host with available resources, not a stress test; no resource limits or OS caches are modified.
instrument.mjschanges the reader worker source in memory through a Node load hook; it does not edit product code. It adds a start milestone and synchronous SQLite duration, observes actual Worker request/reply timings, and retains Effect spans. The start message itself is included in the recorded SQLite duration, preparation is excluded, and roundtrip minus execution must not be called pure transfer time. The schema/HTTP implementation remains the pinned source. Each request has a six-second AbortController boundary; the mid-query probe aborts at the worker's timeline-execution milestone. Bursts await HTTP cleanup before the next burst starts. The diagnostic deliberately does not implement the authenticated mobile client or relay.The scripts below include two publication corrections relative to the first 2×/4× run: metadata histograms explicitly distinguish base/scaled counts, and the writer workload changes the JSON/column timestamp on every UPDATE. The final baseline was rerun with the corrected writer; the old 2×/4× writer phase is explicitly labeled unchanged-value UPDATE attempts in the matrix. Default dependency paths were changed to
source/for public reuse; measured fixture contents and timing logic were otherwise preserved. The verification helper also checks the final changed writer timestamp at baseline.Output:
fixture.json,synthetic.sqlite,results.json(per-request results, worker records, complete captured SQL, Effect spans),summary.json,raw-comparison.json,measurements.md, and optionalcomparison.html. Successful bodies must decode with the actual compact-ID history codec. Unexpected ordinary request errors fail the run; deadline aborts are measured outcomes. The last outcome must show recovery after the explicit cancellation probe.verify-artifacts.mjschecks actual database row counts, response statuses/compact IDs, and the changed writer timestamp.The optional chart helper makes a standalone local HTML comparison from measured results; it starts no server. No scripts instantiate real providers/schedulers/integrations, read a user database, or start a network listener. Scopes dispose HTTP resources and SQLite reader/checkpoint workers when the finite run completes. Keep/delete the external fixture directory according to your normal local cleanup workflow.
instrument.mjs — complete file
reproduce.mjs — complete file
link-dependencies.mjs — complete file
summarize.py — complete file
raw-comparison.mjs — complete file
verify-artifacts.mjs — complete file
make-measurements.py — complete file
SHA-256 of the public script files (UTF-8, LF)
Reported on the user's behalf by GPT-6.1 Sol (high reasoning), through Codex in T3 Code.
Alb11747 commentedon Nov 10, 2026
Note
🤖 GPT-6.1 Sol responding on behalf of Alb11747
November 9 recurrence over direct HTTP: Load earlier turns still hit the 6,000 ms deadline on a long thread served by Windows T3 Code
0.1.8-nightly.20261106.4275.fork.1. This connection did not use the managed relay. The screenshot shows the timeout inline with newer turns still visible.Two matching history GETs used the same cursor and the same
deferWork=1&compactIds=1options. The server traces show:The canceled request acquired a reader in under 0.01 ms. The SQLite span is service-side elapsed time; the 13.75 s HTTP span includes cleanup and is not the query's execution time. This ties the direct-connection timeout to the same history preparation path as the earlier relay report, with the same page succeeding on a later request. It does not establish why the read was slower on the first request.
#20427 tracks the full-thread index rebuild. #24422 proposes reusing that index across pages. That should reduce repeated rebuilds when the cache hits, but the first read on a cache miss still needs to fit within the deadline. The latest published fork build checked (
0.1.8-nightly.20261109.4399.fork.2) still callsreadTimelineIndexbefore page selection and uses the same client deadline; I did not reproduce on that build.