[Bug]: `t3 project` CLI treats any 1s live-server probe failure as "no server": deletes server-runtime.json and writes offline behind a running server
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
What happened
While bulk-registering projects on a Windows machine I read how t3 project add|remove|rename decides between talking to the running server and writing to state.sqlite directly, and found that the live-server probe is bounded to 1 second and that any probe failure (timeout, auth error, undeclared status) is treated the same as "no server": the CLI deletes the running server's server-runtime.json and then applies the mutation offline against the database the live server still has open.
Diagnosis
Grounded in the installed nightly bundle (apps/server/dist/bin.mjs, //#region src/cli/project.ts):
PROJECT_CLI_LIVE_SERVER_TIMEOUT = seconds(1);fetchLiveOrchestrationSnapshotanddispatchLiveOrchestrationCommandare both wrapped inwithProjectCliLiveServerTimeout.tryResolveLiveProjectExecutionModereadsserver-runtime.json, callsfetchLiveOrchestrationSnapshot, and on any failure does:
It does not distinguishlogDebug("Failed to connect to the persisted project CLI server.", ...) yield* clearPersistedServerRuntimeState(config.serverRuntimeStatePath) return none()ECONNREFUSED(server really gone) from a timeout / 401 / 5xx (server alive but slow or rejecting).runProjectMutationthen buildsProjectCliRuntimeLive(its ownOrchestrationEngineover the samestate.sqlite) and dispatches the command inmode: "offline".
Consequences when the probe fails against a live server:
- The running server's
server-runtime.jsonis deleted, sot3 pair,t3 connectand latert3 projectcalls can no longer discover it until the server is restarted (the server only writes the file at startup). - The project mutation is appended to the event store behind the live server's back. The live engine's in-memory read model is not updated, so the project does not appear in the open desktop/web UI until restart, even though the CLI printed "Added project ...".
- Two
OrchestrationEngineinstances (live + offline) write the same event store andprojection_stateconcurrently.
A 1 s bound is easy to miss on Windows: server.getConfig is already known to block for up to 5 s on this platform (#4697, #5137), and orchestration.snapshot grows with projects/threads (64 projects here).
What is verified vs. derived: the code path, constants and the unconditional clearPersistedServerRuntimeState are read directly from the shipped bundle and match src/cli/project.ts on main. I did not force the timeout empirically on this machine (my bulk registration was done with the desktop app fully closed, precisely to avoid this path); the consequences above follow from the code.
Suggested fix (small): only clear the runtime file and fall back to offline when the connection is refused or the recorded pid is not alive; on timeout/auth/undeclared-status errors fail loudly ("live server at did not answer within 1s; retry, or stop the server and rerun") instead of silently writing offline. Optionally raise the bound to the 5 s used by CLOUD_CLI_LIVE_SERVER_TIMEOUT.
Steps to reproduce
Deterministic repro requires making orchestration.snapshot exceed 1 s; by construction:
- Run the desktop app (server writes
<T3 home>/userdata/server-runtime.json). - Make the server slow to answer
orchestration.snapshotfor >1 s (large project/thread count, or pause the server process for 2 s under a debugger). npx t3 project add <some-repo>(ornode apps/server/dist/bin.mjs project add ...).- Observe: CLI prints "Added project ..." in offline mode,
server-runtime.jsonis gone, and the project is absent from the running UI until restart.
Version
0.0.34-nightly.20260819.1132 (commit 36f4314ab768); same code on main today.
Environment
Windows 11 Pro 10.0.26100, T3 Code (Nightly) desktop, Node 24.11.1.
Evidence
# installed bundle, src/cli/project.ts region
const PROJECT_CLI_LIVE_SERVER_TIMEOUT = seconds(1);
const withProjectCliLiveServerTimeout = (effect) => effect.pipe(timeout(PROJECT_CLI_LIVE_SERVER_TIMEOUT));
const fetchLiveOrchestrationSnapshot = (origin, bearerToken) => gen(function* () { ... }).pipe(withProjectCliLiveServerTimeout, mapError(projectCommandErrorFromLiveServerRequest));
const tryResolveLiveProjectExecutionMode = fn$1("tryResolveLiveProjectExecutionMode")(function* (environmentAuth, config) {
const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath);
if (isNone(runtimeState)) return none();
const attempted = yield* result$1(withProjectCliSessionToken(environmentAuth, (token) => fetchLiveOrchestrationSnapshot(runtimeState.value.origin, token).pipe(...)));
if (attempted._tag === "Success") return some(attempted.success);
yield* logDebug("Failed to connect to the persisted project CLI server.", { origin: runtimeState.value.origin, cause: attempted.failure });
yield* clearPersistedServerRuntimeState(config.serverRuntimeStatePath); // <- unconditional
return none();
});
Related observation (not the subject of this issue): after a clean quit of the Windows desktop app (CloseMainWindow on the main window; all T3 Code (Nightly).exe processes exited) server-runtime.json was left behind containing the dead server pid, i.e. the server's release finalizer that normally clears it (//#region src/cli/server.ts, persistServerRuntimeState / clearPersistedServerRuntimeState) did not run on this platform. A stale file is exactly what makes the offline fallback above attractive, but it is also what makes the "any failure" heuristic risky: the CLI cannot tell a stale file from a live-but-slow server.
Related issues
#5749 / #6097 (competing runtimes against the same database) are about the service/desktop split, not the project CLI; no existing issue mentions the 1 s probe or the runtime-file deletion.
Fix applied or workaround
None needed for me: I only used the offline path with the desktop fully closed. Workaround for others: quit T3 completely before using t3 project ..., or check that the project appears in the UI afterwards.
Filed by
Claude (Fable 5) via Claude Code on the reporter's machine, at the reporter's request.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/cli/project.ts by reading PROJECT_CLI_LIVE_SERVER_TIMEOUT, tryResolveLiveProjectExecutionMode, and the live-server request error handling. Trace how runProjectMutation selects offline mode, then reproduce or test timeout and connection-refused cases. Done means live-server failures no longer silently delete server-runtime.json or mutate state.sqlite offline, while genuinely stopped servers still fall back safely.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, sqlite, typescript
- Domain
- backend, cli, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100