authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-25 19:15:06-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-25 19:15:06-07:00
log7c901a37c4e8f35c408a27dbd1bd2efea06a74f5
tree0066e6bdf1d8a10f7a70358796a1fb499d2963e8
parent20885cecf77e205b8931ca1b29f0a2832836a157
signature Signed by PGP key B5690EEEBB952194

fix(cli): t3 triage points agents at log files that exist (#13685)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

8 files changed, 23 insertions(+), 14 deletions(-)

.github/triage/PLAYBOOK.md+3-2
...@@ -59,8 +59,9 @@ different code depending on it:...@@ -59,8 +59,9 @@ different code depending on it:
5959
60Then work from evidence, not assumption. In rough order of value:60Then work from evidence, not assumption. In rough order of value:
6161
62- The server log and the trace file (`server.trace.ndjson`) around the time of the62- The trace file (`server.trace.ndjson`) around the time of the problem, plus the
63 problem. Recent failures usually leave a trail here.63 service log or desktop backend logs from the context file if they exist. Recent
64 failures usually leave a trail here.
64- The provider event log, for problems with claude/codex/cursor sessions.65- The provider event log, for problems with claude/codex/cursor sessions.
65- The SQLite database. Read it freely, but only write when a write is necessary66- The SQLite database. Read it freely, but only write when a write is necessary
66 to fix the problem the user described, and get their explicit permission67 to fix the problem the user described, and get their explicit permission
apps/server/src/cli/config.test.ts-1
...@@ -508,7 +508,6 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {...@@ -508,7 +508,6 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
508 resolved.terminalLogsDir,508 resolved.terminalLogsDir,
509 resolved.attachmentsDir,509 resolved.attachmentsDir,
510 resolved.worktreesDir,510 resolved.worktreesDir,
511 path.dirname(resolved.serverLogPath),
512 path.dirname(resolved.serverTracePath),511 path.dirname(resolved.serverTracePath),
513 ]) {512 ]) {
514 expect(yield* fs.exists(directory)).toBe(true);513 expect(yield* fs.exists(directory)).toBe(true);
apps/server/src/cli/triage.ts+6-1
...@@ -27,6 +27,7 @@ import * as Schema from "effect/Schema";...@@ -27,6 +27,7 @@ import * as Schema from "effect/Schema";
27import { Command, Flag } from "effect/unstable/cli";27import { Command, Flag } from "effect/unstable/cli";
2828
29import packageJson from "../../package.json" with { type: "json" };29import packageJson from "../../package.json" with { type: "json" };
30import * as BootService from "../cloud/bootService.ts";
30import * as ServerConfig from "../config.ts";31import * as ServerConfig from "../config.ts";
31import { resolveBaseDir } from "../os-jank.ts";32import { resolveBaseDir } from "../os-jank.ts";
32import { isProcessAlive, readPersistedServerRuntimeState } from "../serverRuntimeState.ts";33import { isProcessAlive, readPersistedServerRuntimeState } from "../serverRuntimeState.ts";
...@@ -201,7 +202,11 @@ export const triageCommand = Command.make("triage", {...@@ -201,7 +202,11 @@ export const triageCommand = Command.make("triage", {
201 dbPath: paths.dbPath,202 dbPath: paths.dbPath,
202 settingsPath: paths.settingsPath,203 settingsPath: paths.settingsPath,
203 logsDir: paths.logsDir,204 logsDir: paths.logsDir,
204 serverLogPath: paths.serverLogPath,205 // The server writes no log file of its own. Service installs and the
206 // desktop app capture its output. The glob covers every desktop backend
207 // (such as WSL) and rotated copies; names come from DesktopObservability.ts.
208 serviceLogPath: path.join(paths.logsDir, BootService.BOOT_SERVICE_LOG_FILE),
209 desktopBackendLogGlob: path.join(paths.logsDir, "server-child*.log*"),
205 serverTracePath: paths.serverTracePath,210 serverTracePath: paths.serverTracePath,
206 providerEventLogPath: paths.providerEventLogPath,211 providerEventLogPath: paths.providerEventLogPath,
207 terminalLogsDir: paths.terminalLogsDir,212 terminalLogsDir: paths.terminalLogsDir,
apps/server/src/cli/triagePrompt.test.ts+4-1
...@@ -52,7 +52,8 @@ it("context file carries every path the playbook depends on", () => {...@@ -52,7 +52,8 @@ it("context file carries every path the playbook depends on", () => {
52 dbPath: "/home/u/.t3/userdata/state.sqlite",52 dbPath: "/home/u/.t3/userdata/state.sqlite",
53 settingsPath: "/home/u/.t3/userdata/settings.json",53 settingsPath: "/home/u/.t3/userdata/settings.json",
54 logsDir: "/home/u/.t3/userdata/logs",54 logsDir: "/home/u/.t3/userdata/logs",
55 serverLogPath: "/home/u/.t3/userdata/logs/server.log",55 serviceLogPath: "/home/u/.t3/userdata/logs/boot-service.log",
56 desktopBackendLogGlob: "/home/u/.t3/userdata/logs/server-child*.log*",
56 serverTracePath: "/home/u/.t3/userdata/logs/server.trace.ndjson",57 serverTracePath: "/home/u/.t3/userdata/logs/server.trace.ndjson",
57 providerEventLogPath: "/home/u/.t3/userdata/logs/provider/events.log",58 providerEventLogPath: "/home/u/.t3/userdata/logs/provider/events.log",
58 terminalLogsDir: "/home/u/.t3/userdata/logs/terminals",59 terminalLogsDir: "/home/u/.t3/userdata/logs/terminals",
...@@ -63,6 +64,8 @@ it("context file carries every path the playbook depends on", () => {...@@ -63,6 +64,8 @@ it("context file carries every path the playbook depends on", () => {
63 });64 });
64 assert.include(context, "/home/u/.t3/userdata/state.sqlite");65 assert.include(context, "/home/u/.t3/userdata/state.sqlite");
65 assert.include(context, "/home/u/.t3/userdata/logs/server.trace.ndjson");66 assert.include(context, "/home/u/.t3/userdata/logs/server.trace.ndjson");
67 assert.include(context, "/home/u/.t3/userdata/logs/boot-service.log");
68 assert.include(context, "/home/u/.t3/userdata/logs/server-child*.log*");
66 assert.include(context, "/home/u/.t3/userdata/logs/provider/events.log");69 assert.include(context, "/home/u/.t3/userdata/logs/provider/events.log");
67 assert.include(context, "/home/u/.t3/userdata/secrets");70 assert.include(context, "/home/u/.t3/userdata/secrets");
68 assert.include(context, "/home/u/.t3/source");71 assert.include(context, "/home/u/.t3/source");
apps/server/src/cli/triagePrompt.ts+7-4
...@@ -71,8 +71,9 @@ different code depending on it:...@@ -71,8 +71,9 @@ different code depending on it:
7171
72Then work from evidence, not assumption. In rough order of value:72Then work from evidence, not assumption. In rough order of value:
7373
74- The server log and the trace file (\`server.trace.ndjson\`) around the time of the74- The trace file (\`server.trace.ndjson\`) around the time of the problem, plus the
75 problem. Recent failures usually leave a trail here.75 service log or desktop backend logs from the context file if they exist. Recent
76 failures usually leave a trail here.
76- The provider event log, for problems with claude/codex/cursor sessions.77- The provider event log, for problems with claude/codex/cursor sessions.
77- The SQLite database. Read it freely, but only write when a write is necessary78- The SQLite database. Read it freely, but only write when a write is necessary
78 to fix the problem the user described, and get their explicit permission79 to fix the problem the user described, and get their explicit permission
...@@ -176,7 +177,8 @@ export interface TriageContextInput {...@@ -176,7 +177,8 @@ export interface TriageContextInput {
176 readonly dbPath: string;177 readonly dbPath: string;
177 readonly settingsPath: string;178 readonly settingsPath: string;
178 readonly logsDir: string;179 readonly logsDir: string;
179 readonly serverLogPath: string;180 readonly serviceLogPath: string;
181 readonly desktopBackendLogGlob: string;
180 readonly serverTracePath: string;182 readonly serverTracePath: string;
181 readonly providerEventLogPath: string;183 readonly providerEventLogPath: string;
182 readonly terminalLogsDir: string;184 readonly terminalLogsDir: string;
...@@ -205,7 +207,8 @@ Generated by \`t3 triage\` at ${input.generatedAt}....@@ -205,7 +207,8 @@ Generated by \`t3 triage\` at ${input.generatedAt}.
205- Database (SQLite; write only with the user's explicit permission): ${input.paths.dbPath}207- Database (SQLite; write only with the user's explicit permission): ${input.paths.dbPath}
206- Settings: ${input.paths.settingsPath}208- Settings: ${input.paths.settingsPath}
207- Logs dir: ${input.paths.logsDir}209- Logs dir: ${input.paths.logsDir}
208- Server log: ${input.paths.serverLogPath}210- Service log (systemd/launchd service installs only): ${input.paths.serviceLogPath}
211- Desktop backend logs (glob; one file per backend plus rotated copies; written only when a backend crashes or fails to start): ${input.paths.desktopBackendLogGlob}
209- Server trace (ndjson): ${input.paths.serverTracePath}212- Server trace (ndjson): ${input.paths.serverTracePath}
210- Provider event log: ${input.paths.providerEventLogPath}213- Provider event log: ${input.paths.providerEventLogPath}
211- Terminal logs: ${input.paths.terminalLogsDir}214- Terminal logs: ${input.paths.terminalLogsDir}
apps/server/src/cloud/bootService.ts+3-1
...@@ -43,6 +43,8 @@ const BOOT_SERVICE_UNIT_FILE = `${BOOT_SERVICE_NAME}.service`;...@@ -43,6 +43,8 @@ const BOOT_SERVICE_UNIT_FILE = `${BOOT_SERVICE_NAME}.service`;
43const BOOT_SERVICE_LAUNCHD_LABEL = "com.t3tools.t3code.service";43const BOOT_SERVICE_LAUNCHD_LABEL = "com.t3tools.t3code.service";
44const BOOT_SERVICE_PLIST_FILE = `${BOOT_SERVICE_LAUNCHD_LABEL}.plist`;44const BOOT_SERVICE_PLIST_FILE = `${BOOT_SERVICE_LAUNCHD_LABEL}.plist`;
45const BOOT_SERVICE_UNIT_ENV = "T3_BOOT_SERVICE_UNIT";45const BOOT_SERVICE_UNIT_ENV = "T3_BOOT_SERVICE_UNIT";
46/** File in the logs dir that receives the service's stdout and stderr. `t3 triage` points agents at it. */
47export const BOOT_SERVICE_LOG_FILE = "boot-service.log";
4648
47/** systemd expands `%` specifiers, including in unquoted append-log paths. */49/** systemd expands `%` specifiers, including in unquoted append-log paths. */
48function escapeSystemdSpecifiers(value: string): string {50function escapeSystemdSpecifiers(value: string): string {
...@@ -599,7 +601,7 @@ export const make = Effect.fn("cloud.boot_service.make")(function* (input: {...@@ -599,7 +601,7 @@ export const make = Effect.fn("cloud.boot_service.make")(function* (input: {
599 environmentPath,601 environmentPath,
600 });602 });
601 const unitPath = detectedManager?.unitPath ?? "";603 const unitPath = detectedManager?.unitPath ?? "";
602 const logPath = path.join(input.logsDir, "boot-service.log");604 const logPath = path.join(input.logsDir, BOOT_SERVICE_LOG_FILE);
603 const statePath = path.join(input.baseDir, "runtime", SERVICE_STATE_FILE);605 const statePath = path.join(input.baseDir, "runtime", SERVICE_STATE_FILE);
604 const restartPendingPath = path.join(input.baseDir, "runtime", SERVICE_RESTART_PENDING_FILE);606 const restartPendingPath = path.join(input.baseDir, "runtime", SERVICE_RESTART_PENDING_FILE);
605 const runtimePaths = pinnedRuntimePaths(path, input.baseDir, input.cliVersion, platform);607 const runtimePaths = pinnedRuntimePaths(path, input.baseDir, input.cliVersion, platform);
apps/server/src/config.ts-2
...@@ -44,7 +44,6 @@ export interface ServerDerivedPaths {...@@ -44,7 +44,6 @@ export interface ServerDerivedPaths {
44 /** Screenshots the agent asks the collaborative browser to keep for the user. */44 /** Screenshots the agent asks the collaborative browser to keep for the user. */
45 readonly browserArtifactsDir: string;45 readonly browserArtifactsDir: string;
46 readonly logsDir: string;46 readonly logsDir: string;
47 readonly serverLogPath: string;
48 readonly serverTracePath: string;47 readonly serverTracePath: string;
49 readonly providerLogsDir: string;48 readonly providerLogsDir: string;
50 readonly providerEventLogPath: string;49 readonly providerEventLogPath: string;
...@@ -154,7 +153,6 @@ export const deriveServerPaths = Effect.fn(function* (...@@ -154,7 +153,6 @@ export const deriveServerPaths = Effect.fn(function* (
154 attachmentsDir,153 attachmentsDir,
155 browserArtifactsDir: join(stateDir, "browser-artifacts"),154 browserArtifactsDir: join(stateDir, "browser-artifacts"),
156 logsDir,155 logsDir,
157 serverLogPath: join(logsDir, "server.log"),
158 serverTracePath: join(logsDir, "server.trace.ndjson"),156 serverTracePath: join(logsDir, "server.trace.ndjson"),
159 providerLogsDir,157 providerLogsDir,
160 providerEventLogPath: join(providerLogsDir, "events.log"),158 providerEventLogPath: join(providerLogsDir, "events.log"),
docs/operations/observability.md-2
...@@ -603,5 +603,3 @@ Current high-value span and metric boundaries include:...@@ -603,5 +603,3 @@ Current high-value span and metric boundaries include:
603- logs outside spans are not persisted in the trace file; SSH-managed launch stdout/stderr is still603- logs outside spans are not persisted in the trace file; SSH-managed launch stdout/stderr is still
604 captured in its launcher log604 captured in its launcher log
605- metrics are not snapshotted locally605- metrics are not snapshotted locally
606- the old `serverLogPath` still exists in config for compatibility, but the trace file is the primary
607 structured persisted artifact