Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
c7b375e
[codex] Structure Electron window failures (#3276)
juliusmarminge Jun 20, 2026
4407a5a
[codex] Structure Codex shadow home errors (#3262)
juliusmarminge Jun 20, 2026
4c16c66
[codex] Structure ACP transport errors (#3251)
juliusmarminge Jun 20, 2026
7b79189
[codex] Structure relay auth persistence errors (#3250)
juliusmarminge Jun 20, 2026
c1d8a22
[codex] Structure desktop persisted credential errors (#3239)
juliusmarminge Jun 20, 2026
bd8e3ee
Align text generation error catches (#3292)
juliusmarminge Jun 20, 2026
d7ff7e7
[codex] Audit managed endpoint error context (#3245)
juliusmarminge Jun 20, 2026
569b3e5
[codex] Structure desktop SSH prompt presentation failures (#3429)
juliusmarminge Jun 20, 2026
630df6b
[codex] Enrich source-control errors (#3248)
juliusmarminge Jun 20, 2026
6216162
[codex] Sanitize text generation CLI errors (#3431)
juliusmarminge Jun 20, 2026
eb5eb0d
[codex] Structure managed endpoint allocation failures (#3421)
juliusmarminge Jun 20, 2026
be56fa4
[codex] Preserve workspace root stat failures (#3278)
juliusmarminge Jun 20, 2026
6e9b43c
Preserve trace file read causes (#3300)
juliusmarminge Jun 20, 2026
c637dfc
[codex] enrich Git workflow errors (#3241)
juliusmarminge Jun 20, 2026
fc2cdec
[codex] Structure preview automation boundary failures (#3436)
juliusmarminge Jun 20, 2026
b51aef1
[codex] Preserve cloud disconnect diagnostics (#3437)
juliusmarminge Jun 20, 2026
b6c590a
[codex] Fix desktop preview event delivery errors (#3435)
juliusmarminge Jun 20, 2026
6d8d995
[codex] Correlate protocol request failures (#3433)
juliusmarminge Jun 20, 2026
dd48bfd
[codex] Structure server settings failures (#3376)
juliusmarminge Jun 20, 2026
faccdd4
[codex] Preserve desktop backend log failures (#3375)
juliusmarminge Jun 20, 2026
d1339f3
[codex] Structure client state key errors (#3374)
juliusmarminge Jun 20, 2026
5076768
[codex] Preserve terminal preview link failure context (#3367)
juliusmarminge Jun 20, 2026
58053d1
[codex] Structure terminal PTY operation failures (#3364)
juliusmarminge Jun 20, 2026
b888735
[codex] Structure cross-client clipboard failures (#3361)
juliusmarminge Jun 20, 2026
d7718e8
[codex] Structure mobile notification navigation failures (#3359)
juliusmarminge Jun 20, 2026
97de7d7
[codex] Structure agent awareness registration errors (#3328)
juliusmarminge Jun 20, 2026
a51691a
[codex] Structure server environment-label probe failures (#3321)
juliusmarminge Jun 20, 2026
a76b7bb
Preserve PortScanner probe defects (#3282)
juliusmarminge Jun 20, 2026
aab72f1
[codex] Report markdown interaction failures (#3355)
juliusmarminge Jun 20, 2026
9a78c6f
[codex] Structure web local storage failures (#3350)
juliusmarminge Jun 20, 2026
8ce627b
[codex] Structure mobile thread outbox failures (#3341)
juliusmarminge Jun 20, 2026
834e5db
[codex] structure project CLI failures (#3339)
juliusmarminge Jun 20, 2026
42ff439
[codex] Preserve mobile review highlighter failures (#3337)
juliusmarminge Jun 20, 2026
90f1936
[codex] Structure server runtime-state failures (#3319)
juliusmarminge Jun 20, 2026
e042c25
[codex] Structure rotating log sink errors (#3279)
juliusmarminge Jun 20, 2026
f2cb14e
[codex] Structure mobile relay token-store failures (#3309)
juliusmarminge Jun 20, 2026
674590e
[codex] Fix terminal cwd error test construction (#3440)
juliusmarminge Jun 20, 2026
ad2cb1d
[codex] Diagnose desktop client settings read failures (#3432)
juliusmarminge Jun 20, 2026
60c9ca0
[codex] Preserve checkpoint repository detection failures (#3360)
juliusmarminge Jun 20, 2026
6ff6c13
[codex] structure desktop backend process errors (#3254)
juliusmarminge Jun 20, 2026
a26f0dc
[codex] structure source-control repository failures (#3336)
juliusmarminge Jun 20, 2026
57d25c9
[codex] Structure persistence error correlation (#3439)
juliusmarminge Jun 20, 2026
90dc76b
Preserve asset access failure causes (#3342)
juliusmarminge Jun 20, 2026
5edf7c5
[codex] Preserve PR materialization failure chains (#3443)
juliusmarminge Jun 20, 2026
a9460bb
[codex] Structure pull request link failures (#3445)
juliusmarminge Jun 20, 2026
28e7c9a
[codex] Structure mobile waitlist enrollment failures (#3446)
juliusmarminge Jun 20, 2026
803c2b7
[codex] Preserve desktop backend output read failures (#3444)
juliusmarminge Jun 20, 2026
9243ead
[codex] Preserve Linux icon resize fallback failures (#3447)
juliusmarminge Jun 21, 2026
9f7861a
[codex] Structure desktop update persistence errors (#3261)
juliusmarminge Jun 21, 2026
4cd9584
[codex] Migrate desktop app errors to Schema (#3449)
juliusmarminge Jun 21, 2026
68c0dd3
[codex] Structure server CLI failures (#3450)
juliusmarminge Jun 21, 2026
0eba548
[codex] Model asset access failures with distinct errors (#3448)
juliusmarminge Jun 21, 2026
0488e47
[codex] Structure checkpoint diff failures (#3453)
juliusmarminge Jun 21, 2026
23ab75e
[codex] Split project command failures (#3459)
juliusmarminge Jun 21, 2026
8d4a8b4
[codex] Structure preview capability errors (#3454)
juliusmarminge Jun 21, 2026
6424197
[codex] Structure unroutable app-server messages (#3463)
juliusmarminge Jun 21, 2026
1a59277
[codex] Structure GitLab CLI failures (#3458)
juliusmarminge Jun 21, 2026
a3dadc0
[codex] Structure Bitbucket API errors (#3457)
juliusmarminge Jun 21, 2026
2fbb056
[codex] Structure workspace search cleanup failures (#3465)
juliusmarminge Jun 21, 2026
cdd0359
[codex] Structure release metadata failures (#3467)
juliusmarminge Jun 21, 2026
6fc7973
[codex] Structure mobile native static-check failures (#3464)
juliusmarminge Jun 21, 2026
d389cfd
[codex] Structure Azure DevOps CLI failures (#3460)
juliusmarminge Jun 21, 2026
60dc4af
[codex] Structure desktop build script failures (#3452)
juliusmarminge Jun 21, 2026
8c2d33a
[codex] Structure release package updater failures (#3468)
juliusmarminge Jun 21, 2026
1b2f39d
[codex] Structure theme synchronization failures (#3466)
juliusmarminge Jun 21, 2026
61e6d89
[codex] Structure GitHub CLI failures (#3456)
juliusmarminge Jun 21, 2026
e4a84a3
[codex] Structure release output failures (#3470)
juliusmarminge Jun 21, 2026
cebbe6f
[codex] Preserve child process termination context (#3469)
juliusmarminge Jun 21, 2026
3c246f5
[codex] Structure VCS process boundary errors (#3476)
juliusmarminge Jun 21, 2026
e3970e7
[codex] Preserve APNs delivery failure context (#3475)
juliusmarminge Jun 21, 2026
6155f5c
[codex] Preserve VCS project config error causes (#3474)
juliusmarminge Jun 21, 2026
90074e3
[codex] Preserve desktop update state causes (#3473)
juliusmarminge Jun 21, 2026
8c1605b
[codex] Structure OpenCode text generation failures (#3472)
juliusmarminge Jun 21, 2026
2a29de7
[codex] Structure primary auth validation failures (#3471)
juliusmarminge Jun 21, 2026
0debeba
[codex] Structure thread archive blocked error (#3451)
juliusmarminge Jun 21, 2026
61f8d46
[codex] Enforce Effect error handling conventions (#3380)
juliusmarminge Jun 21, 2026
82a9bcc
[codex] add session context to credential errors (#3349)
juliusmarminge Jun 21, 2026
3afe123
sync: merge upstream runtime diagnostics
Jun 23, 2026
f0af75a
Fix preview CI checks
Jun 24, 2026
d6ca282
Fix provider diagnostics checks
Jun 24, 2026
0349fd1
Format runtime diagnostics files
Jun 24, 2026
7958fed
Merge branch 'sync/upstream-20260620-structured-errors-b' into sync/u…
Jun 24, 2026
8b402fb
Merge branch 'sync/upstream-20260620-structured-errors-b' into sync/u…
Jun 24, 2026
b355575
Merge branch 'sync/upstream-20260620-structured-errors-b' into sync/u…
Jun 24, 2026
c69ea36
Merge branch 'sync/upstream-20260620-structured-errors-b' into sync/u…
Jun 24, 2026
77581d0
Merge branch 'sync/upstream-20260620-structured-errors-b' into sync/u…
Jun 24, 2026
3b59e6c
Merge branch 'sync/upstream-20260620-structured-errors-b' into sync/u…
Jun 24, 2026
9c097bb
Merge branch 'sync/upstream-20260620-structured-errors-b' into sync/u…
Jun 24, 2026
c50e979
Merge branch 'sync/upstream-20260620-structured-errors-b' into sync/u…
Jun 24, 2026
1abd42a
Merge branch 'sync/upstream-20260620-structured-errors-b' into sync/u…
Jun 24, 2026
da55dd1
Merge branch 'sync/upstream-20260620-structured-errors-b' into sync/u…
Jun 24, 2026
2e8156b
Merge branch 'sync/upstream-20260620-structured-errors-b' into sync/u…
Jun 24, 2026
85cc80f
Merge branch 'sync/upstream-20260620-structured-errors-b' into sync/u…
Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .macroscope/check-run-agents/effect-service-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,19 @@ Review changed TypeScript and directly affected call sites for the conventions b
- Define service failures with `Schema.TaggedErrorClass` and structured attributes. Derive `message` from those attributes rather than storing an unstructured message as the only data.
- `Schema.Defect()` is not a substitute for modeling a generic error: its tag, fields, or both must identify the failure structurally, and its `message` must not merely stringify an opaque cause. A semantically precise error tag may preserve a real `cause` without inventing a redundant singleton field when no additional variable context exists; still retain any real path, resource, request, or entity context available at the wrapping site.
- Capture stable, serializable domain context such as the operation or stage, resource/path or entity identifier, and normalized category/status. Map failures where that context is known instead of wrapping an entire multi-step pipeline in one generic error. Do not add a `detail` field that merely copies `cause.message` and then use it to construct the wrapper message.
- Keep direct error attributes and log annotations safe and bounded. Do not copy raw wire payloads, command arguments or output, signed URLs, credentials, query strings, fragments, selectors, or arbitrary defect text into `detail`, `reason`, `message`, or a parallel log payload. Preserve the exact underlying value only as `cause`; expose normalized categories plus lengths/counts and safe URL protocol/hostname diagnostics where useful. Logging a sanitized error must not reintroduce a removed legacy `detail` or serialized `cause` field beside it.
- When translating or wrapping a real failure, preserve the immediate underlying error itself as `cause` alongside the structural fields so the complete error chain and stack remain available. If every construction wraps a failure, `cause` should be required; make it optional only when the same error can legitimately originate without an underlying failure.
- At a translation boundary, pass through an already structured domain error when it is part of the declared target error channel. Wrap only unknown or genuinely lower-level failures. A static factory or mapper may perform this classification when it is reused and keeps the policy next to the target error type.
- Derive the wrapper's `message` exclusively from its stable structural attributes, never from `cause`, `cause.message`, or a stringified defect. Do not replace the immediate error with only `error.cause`, erase a structured upstream error into a string, or manufacture an `Error` merely to populate `cause`. Pure validation/domain errors created without an underlying failure do not need a cause.
- Do not encode the same distinction twice with both a specific error tag and a single-value `operation`, `reason`, `kind`, or `phase` literal. Choose one coherent model: use distinct error classes and omit the redundant discriminator when callers or messages treat the failures as genuinely different, or use one service-level error with a multi-value operation discriminator and a generic message derived from that operation when the failures share the same semantics.
- Treat an error message exposed through an HTTP/RPC response, persisted state, UI, or another caller-visible boundary as behavior. Preserve those messages during a structural refactor. Existing distinct caller-visible messages are evidence that the failures should normally remain distinct error tags without redundant singleton discriminators, rather than being collapsed into a generic operation error.
- Split semantically distinct failures into separate error classes when a `reason`, `kind`, `phase`, or similar discriminator is used to choose the user-facing message or drive caller control flow. A discriminator used only for internal diagnostics may remain a field.
- Use `Schema.Union` of error classes when a shared schema, predicate, or helper type is useful.
- Export direct schema predicates such as `export const isFoo = Schema.is(Foo)`. Flag a private `Schema.is` constant wrapped by a redundant function with the same signature.
- Do not introduce a large `switch` or lookup table in an error's `message` getter to model failures that deserve separate error classes.
- Catch statically known tagged failures with `Effect.catchTags({ ... })`, including when handling only one tag. Do not use `catchIf` with a schema predicate merely to recover one or more known `_tag` variants, and do not use `catchTag`. `Effect.catch` is appropriate when the entire error channel is intentionally handled; `catchIf` remains appropriate for genuinely structural predicates such as inspecting an underlying platform error code.
- Do not add a helper whose only behavior is `(...args) => new SomeError({ ...args })`, including curried aliases used once with `mapError`. Construct the error at the failure boundary so its attributes and cause remain visible. Keep a mapper only when it performs real normalization, passes through existing domain errors, or adds reusable context/control flow.
- When a reusable error-to-error translation clearly belongs to the target error type, prefer a descriptive static factory on that error class over a detached production-side switch. Do not force a static method for one-off inline mappings.

## File layout and migrations

Expand All @@ -73,4 +78,6 @@ Review changed TypeScript and directly affected call sites for the conventions b

## Reporting

Report only concrete violations introduced or retained in the pull request's changed scope. Prefer precise inline comments on the smallest relevant line range and state the expected fix. A clear convention violation may fail the check. Do not fail for optional style preferences or unrelated legacy code. If there are no findings, report exactly `All clear`.
Report only concrete violations introduced or retained in the pull request's changed scope. Prefer precise inline comments on the smallest relevant line range and state the expected fix. A clear convention violation may fail the check. Do not fail for optional style preferences or unrelated legacy code.

This check defaults to failure. When there are no findings, stop immediately and make the entire final response exactly `All clear` on one line. Do not add a title, explanation, punctuation, Markdown, JSON, or trailing analysis, and do not continue reasoning after deciding the review is clean.
24 changes: 13 additions & 11 deletions apps/desktop/src/app/DesktopApp.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as Cause from "effect/Cause";
import * as Data from "effect/Data";
import * as Effect from "effect/Effect";
import * as Option from "effect/Option";
import * as Ref from "effect/Ref";
import * as Schema from "effect/Schema";

import * as NetService from "@t3tools/shared/Net";
import * as Crypto from "effect/Crypto";
Expand Down Expand Up @@ -33,22 +33,24 @@ const makeDesktopRunId = Crypto.Crypto.pipe(
Effect.map((value) => value.replaceAll("-", "").slice(0, 12)),
);

class DesktopBackendPortUnavailableError extends Data.TaggedError(
export class DesktopBackendPortUnavailableError extends Schema.TaggedErrorClass<DesktopBackendPortUnavailableError>()(
"DesktopBackendPortUnavailableError",
)<{
readonly startPort: number;
readonly maxPort: number;
readonly hosts: readonly string[];
}> {
override get message() {
{
startPort: Schema.Int,
maxPort: Schema.Int,
hosts: Schema.Array(Schema.String),
},
) {
override get message(): string {
return `No desktop backend port is available on hosts ${this.hosts.join(", ")} between ${this.startPort} and ${this.maxPort}.`;
}
}

class DesktopDevelopmentBackendPortRequiredError extends Data.TaggedError(
export class DesktopDevelopmentBackendPortRequiredError extends Schema.TaggedErrorClass<DesktopDevelopmentBackendPortRequiredError>()(
"DesktopDevelopmentBackendPortRequiredError",
)<{}> {
override get message() {
{},
) {
override get message(): string {
return "T3CODE_PORT is required in desktop development.";
}
}
Expand Down
30 changes: 30 additions & 0 deletions apps/desktop/src/app/DesktopAppErrors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { assert, describe, it } from "@effect/vitest";

import {
DesktopBackendPortUnavailableError,
DesktopDevelopmentBackendPortRequiredError,
} from "./DesktopApp.ts";

describe("DesktopApp errors", () => {
it("preserves unavailable backend port context", () => {
const error = new DesktopBackendPortUnavailableError({
startPort: 3_773,
maxPort: 65_535,
hosts: ["127.0.0.1", "0.0.0.0", "::"],
});

assert.equal(error.startPort, 3_773);
assert.equal(error.maxPort, 65_535);
assert.deepEqual(error.hosts, ["127.0.0.1", "0.0.0.0", "::"]);
assert.equal(
error.message,
"No desktop backend port is available on hosts 127.0.0.1, 0.0.0.0, :: between 3773 and 65535.",
);
});

it("reports the required development port", () => {
const error = new DesktopDevelopmentBackendPortRequiredError();

assert.equal(error.message, "T3CODE_PORT is required in desktop development.");
});
});
122 changes: 122 additions & 0 deletions apps/desktop/src/app/DesktopBackendOutputLog.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { assert, describe, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Logger from "effect/Logger";
import * as Path from "effect/Path";
import * as PlatformError from "effect/PlatformError";

import * as DesktopBackendOutputLog from "./DesktopBackendOutputLog.ts";
import * as DesktopConfig from "./DesktopConfig.ts";
import * as DesktopEnvironment from "./DesktopEnvironment.ts";

const LOG_FILE_PATH = "/Users/alice/.t3/userdata/logs/server-child.log";

const environmentLayer = DesktopEnvironment.layer({
dirname: "/repo/apps/desktop/dist-electron",
homeDirectory: "/Users/alice",
platform: "darwin",
processArch: "arm64",
appVersion: "1.2.3",
appPath: "/Applications/T3 Code.app/Contents/Resources/app.asar",
isPackaged: true,
resourcesPath: "/Applications/T3 Code.app/Contents/Resources",
runningUnderArm64Translation: false,
}).pipe(Layer.provide(Layer.merge(Path.layer, DesktopConfig.layerTest({}))));

const withOutputLog = <A, E, R>(
effect: Effect.Effect<A, E, R | DesktopBackendOutputLog.DesktopBackendOutputLog>,
fileSystemLayer: Layer.Layer<FileSystem.FileSystem>,
messages: Array<ReadonlyArray<unknown>>,
) => {
const logger = Logger.make(({ message }) => {
messages.push(Array.isArray(message) ? message : [message]);
});
const outputLogLayer = DesktopBackendOutputLog.layer.pipe(
Layer.provide(Layer.mergeAll(fileSystemLayer, Path.layer, environmentLayer)),
Layer.provideMerge(Logger.layer([logger], { mergeWithExisting: false })),
);
return effect.pipe(Effect.provide(outputLogLayer));
};

const loggedError = (messages: ReadonlyArray<ReadonlyArray<unknown>>): unknown =>
messages.flat().find((value) => typeof value === "object" && value !== null && "error" in value)
?.error;

describe("DesktopBackendOutputLog", () => {
it.effect("logs setup failures with the log path and exact cause", () => {
const messages: Array<ReadonlyArray<unknown>> = [];
const cause = PlatformError.systemError({
_tag: "PermissionDenied",
module: "FileSystem",
method: "makeDirectory",
pathOrDescriptor: "/Users/alice/.t3/userdata/logs",
description: "private setup diagnostic",
});
const fileSystemLayer = FileSystem.layerNoop({
makeDirectory: () => Effect.fail(cause),
});

return withOutputLog(
Effect.gen(function* () {
const outputLog = yield* DesktopBackendOutputLog.DesktopBackendOutputLog;
yield* outputLog.writeSessionBoundary({ phase: "START", details: "test" });

const error = loggedError(messages);
assert.instanceOf(error, DesktopBackendOutputLog.DesktopBackendOutputLogSetupError);
assert.equal(error.logFilePath, LOG_FILE_PATH);
assert.strictEqual(error.cause, cause);
assert.equal(
error.message,
`Failed to initialize the desktop backend output log at ${LOG_FILE_PATH}.`,
);
assert.notInclude(error.message, "private setup diagnostic");
}),
fileSystemLayer,
messages,
);
});

it.effect("logs record write failures with the operation and exact cause", () => {
const messages: Array<ReadonlyArray<unknown>> = [];
const missingCause = PlatformError.systemError({
_tag: "NotFound",
module: "FileSystem",
method: "stat",
pathOrDescriptor: LOG_FILE_PATH,
});
const writeCause = PlatformError.systemError({
_tag: "PermissionDenied",
module: "FileSystem",
method: "writeFile",
pathOrDescriptor: LOG_FILE_PATH,
description: "private write diagnostic",
});
const fileSystemLayer = FileSystem.layerNoop({
makeDirectory: () => Effect.void,
stat: () => Effect.fail(missingCause),
readDirectory: () => Effect.succeed([]),
writeFile: () => Effect.fail(writeCause),
});

return withOutputLog(
Effect.gen(function* () {
const outputLog = yield* DesktopBackendOutputLog.DesktopBackendOutputLog;
yield* outputLog.writeSessionBoundary({ phase: "START", details: "test" });

const error = loggedError(messages);
assert.instanceOf(error, DesktopBackendOutputLog.DesktopBackendOutputLogWriteError);
assert.equal(error.operation, "write-record");
assert.equal(error.logFilePath, LOG_FILE_PATH);
assert.strictEqual(error.cause, writeCause);
assert.equal(
error.message,
`Desktop backend output log operation "write-record" failed at ${LOG_FILE_PATH}.`,
);
assert.notInclude(error.message, "private write diagnostic");
}),
fileSystemLayer,
messages,
);
});
});
Loading
Loading