Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion apps/desktop/src/backend/DesktopBackendManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ function drainBackendOutput(
cause,
}),
),
Effect.catchTag("BackendProcessOutputHandlingError", onOutputFailure),
Effect.catchTags({ BackendProcessOutputHandlingError: onOutputFailure }),
),
),
Effect.catchTags({
Expand Down
9 changes: 5 additions & 4 deletions apps/server/src/auth/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ export const layerAuthenticatedAuth = Layer.effect(
}),
session.subject === "cloud-connect" ? traceAuthenticatedRelayRequest : identity,
);
}).pipe(Effect.catchTag("EnvironmentAuthInvalidError", appendDpopChallengeOnUnauthorized));
}).pipe(Effect.catchTags({ EnvironmentAuthInvalidError: appendDpopChallengeOnUnauthorized }));
}),
);

Expand Down Expand Up @@ -474,9 +474,10 @@ export const layer = HttpApiBuilder.group(
);
return { revoked };
},
Effect.catchTag("ServerAuthForbiddenOperationError", () =>
failEnvironmentOperationForbidden("current_session_revoke_not_allowed"),
),
Effect.catchTags({
ServerAuthForbiddenOperationError: () =>
failEnvironmentOperationForbidden("current_session_revoke_not_allowed"),
}),
Effect.catchIf(EnvironmentAuth.isServerAuthInternalError, (error) =>
failEnvironmentInternal("client_session_revoke_failed", error),
),
Expand Down
11 changes: 6 additions & 5 deletions apps/server/src/cli/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,12 @@ const authorizeCli = Effect.fn("cloud.cli.authorize")(function* (options: {
// A stored credential whose refresh fails (revoked, expired grant) must
// fall through to a fresh device authorization, not dead-end the command.
const existing = yield* tokens.getExisting.pipe(
Effect.catchTag("CloudCliCredentialRefreshError", () =>
Console.log(
"The stored T3 Connect credential could not be refreshed; signing in again.",
).pipe(Effect.as(Option.none())),
),
Effect.catchTags({
CloudCliCredentialRefreshError: () =>
Console.log(
"The stored T3 Connect credential could not be refreshed; signing in again.",
).pipe(Effect.as(Option.none())),
}),
);
if (Option.isSome(existing)) {
return existing.value.identity ?? null;
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/cli/uninstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ const runUninstall = Effect.fn("cli.uninstall.run")(function* (input: {
}
const confirmed = yield* Prompt.run(
Prompt.Confirm({ message: "Remove t3 from this machine?", initial: false }),
).pipe(Effect.catchTag("QuitError", () => Effect.succeed(false)));
).pipe(Effect.catchTags({ QuitError: () => Effect.succeed(false) }));
if (!confirmed) {
yield* Console.log("Left as is.");
return;
Expand Down
4 changes: 2 additions & 2 deletions apps/server/src/cli/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ const runUpdate = Effect.fn("cli.update.run")(function* (input: {
}
const confirmed = yield* Prompt.run(
Prompt.Confirm({ message: "Install the preview build anyway?", initial: false }),
).pipe(Effect.catchTag("QuitError", () => Effect.succeed(false)));
).pipe(Effect.catchTags({ QuitError: () => Effect.succeed(false) }));
if (!confirmed) {
yield* Console.log("Left as is.");
return;
Expand Down Expand Up @@ -473,7 +473,7 @@ const runUpdate = Effect.fn("cli.update.run")(function* (input: {
message: "Restart the background service once the download is verified?",
initial: true,
}),
).pipe(Effect.catchTag("QuitError", () => Effect.succeed(false)));
).pipe(Effect.catchTags({ QuitError: () => Effect.succeed(false) }));
} else {
yield* Console.log(
" Not a terminal, so the service keeps running its current version. Rerun with --yes to restart it now, or run `t3 service restart` later.",
Expand Down
12 changes: 6 additions & 6 deletions apps/server/src/cloud/CliTokenManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,9 +408,9 @@ export const deviceAuthorizationLogin = Effect.fn("cloud.cli_token.device_author
});
return yield* pollDeviceToken(metadata, authorization.device_code, interval).pipe(
Effect.timeout(expiresIn),
Effect.catchTag("TimeoutError", (cause) =>
Effect.fail(new CloudCliAuthorizationTimeoutError({ cause })),
),
Effect.catchTags({
TimeoutError: (cause) => Effect.fail(new CloudCliAuthorizationTimeoutError({ cause })),
}),
);
},
);
Expand Down Expand Up @@ -505,9 +505,9 @@ export const make = Effect.gen(function* () {
authorizationUrl,
callback: Deferred.await(callback).pipe(
Effect.timeout(CLOUD_CLI_OAUTH_CALLBACK_TIMEOUT),
Effect.catchTag("TimeoutError", (cause) =>
Effect.fail(new CloudCliAuthorizationTimeoutError({ cause })),
),
Effect.catchTags({
TimeoutError: (cause) => Effect.fail(new CloudCliAuthorizationTimeoutError({ cause })),
}),
),
terminal,
launchBrowser: externalLauncher.launchBrowser,
Expand Down
6 changes: 3 additions & 3 deletions apps/server/src/cloud/CloudLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ const make = Effect.gen(function* () {
return proof satisfies RelayEnvironmentLinkProof;
},
Effect.catchIf(ServerSecretStore.isSecretStoreError, internalError("generate-link-proof")),
Effect.catchTag("PlatformError", internalError("generate-link-proof")),
Effect.catchTags({ PlatformError: internalError("generate-link-proof") }),
);

const activateManagedTunnel = Effect.fn("environment.cloud.activateManagedTunnel")(
Expand Down Expand Up @@ -1546,7 +1546,7 @@ const make = Effect.gen(function* () {
} satisfies RelayEnvironmentHealthResponse;
},
Effect.catchIf(ServerSecretStore.isSecretStoreError, internalError("answer-health")),
Effect.catchTag("PlatformError", internalError("answer-health")),
Effect.catchTags({ PlatformError: internalError("answer-health") }),
);

const mintCredential = Effect.fn("environment.cloud.mintCredential")(
Expand Down Expand Up @@ -1653,7 +1653,7 @@ const make = Effect.gen(function* () {
} satisfies RelayEnvironmentMintResponse;
},
Effect.catchIf(ServerSecretStore.isSecretStoreError, internalError("issue-credential")),
Effect.catchTag("PlatformError", internalError("issue-credential")),
Effect.catchTags({ PlatformError: internalError("issue-credential") }),
);

return CloudLink.of({
Expand Down
38 changes: 20 additions & 18 deletions apps/server/src/htmlRender/HtmlRender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,26 +351,28 @@ const make = Effect.gen(function* () {
browsers.withPermits(1)(
// Read once a permit is held, so a queued call sees a fallback learned meanwhile.
Effect.suspend(() => run(noSandbox)).pipe(
Effect.catchTag("HtmlRenderSandboxUnavailableError", () =>
Effect.logInfo(
"Chrome's sandbox is unavailable on this host; launching it without one.",
).pipe(
Effect.andThen(
Effect.sync(() => {
noSandbox = true;
Effect.catchTags({
HtmlRenderSandboxUnavailableError: () =>
Effect.logInfo(
"Chrome's sandbox is unavailable on this host; launching it without one.",
).pipe(
Effect.andThen(
Effect.sync(() => {
noSandbox = true;
}),
),
Effect.andThen(run(true)),
),
}),
Effect.catchTags({
HtmlRenderSandboxUnavailableError: (cause) =>
Effect.fail(
new HeadlessChrome.HtmlRenderBrowserError({
reason: "the browser has no sandbox",
cause,
}),
),
Effect.andThen(run(true)),
),
),
Effect.catchTag("HtmlRenderSandboxUnavailableError", (cause) =>
Effect.fail(
new HeadlessChrome.HtmlRenderBrowserError({
reason: "the browser has no sandbox",
cause,
}),
),
),
}),
),
);

Expand Down
22 changes: 12 additions & 10 deletions apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3011,16 +3011,18 @@ export function makeOpenCodeAdapterV2(
);
yield* Effect.raceFirst(Fiber.join(request), Deferred.await(receipt)).pipe(
Effect.timeout("10 seconds"),
Effect.catchTag("TimeoutError", (cause) => {
const error = new OpenCodeRuntime.OpenCodeRuntimeError({
operation: "session.command",
detail: "OpenCode command admission did not complete within 10 seconds.",
cause,
});
abortController.abort();
return finalizeTurn(state, turn, "failed", {
failure: makeProviderFailure({ cause: error, class: "provider_error" }),
}).pipe(Effect.andThen(Effect.fail(error)));
Effect.catchTags({
TimeoutError: (cause) => {
const error = new OpenCodeRuntime.OpenCodeRuntimeError({
operation: "session.command",
detail: "OpenCode command admission did not complete within 10 seconds.",
cause,
});
abortController.abort();
return finalizeTurn(state, turn, "failed", {
failure: makeProviderFailure({ cause: error, class: "provider_error" }),
}).pipe(Effect.andThen(Effect.fail(error)));
},
}),
);
});
Expand Down
7 changes: 3 additions & 4 deletions apps/server/src/provider/acp/AcpRegistrySupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1916,16 +1916,15 @@ export const makeAcpRegistryCatalog = Effect.fn("AcpRegistryCatalog.make")(funct
),
)
.pipe(
Effect.catchTag(
"ServerSettingsError",
(cause) =>
Effect.catchTags({
ServerSettingsError: (cause) =>
new AcpRegistryError({
reason: "install_failed",
detail:
"Could not read provider settings while checking managed ACP binary references.",
cause,
}),
),
}),
);

return AcpRegistryCatalog.of({
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/pullRequest/PullRequestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1578,7 +1578,7 @@ export const make = Effect.gen(function* () {
? operation.pipe(Effect.provideService(routingCredential, identity), Effect.result)
: Effect.fail(rejected()),
)
.pipe(Effect.catchTag("PullRequestProviderError", () => Effect.fail(rejected())));
.pipe(Effect.catchTags({ PullRequestProviderError: () => Effect.fail(rejected()) }));
return yield* Effect.fromResult(result);
});

Expand Down
15 changes: 8 additions & 7 deletions apps/server/src/workspace/WorkspaceSearchIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,13 +376,14 @@ export const make = Effect.fn("WorkspaceSearchIndex.make")(function* (
cause,
}),
).pipe(
Effect.catchTag("WorkspaceSearchIndexScanTimedOut", (error) =>
variant === "paths"
? Effect.sync(() => {
initialScanTimedOut = true;
})
: Effect.fail(error),
),
Effect.catchTags({
WorkspaceSearchIndexScanTimedOut: (error) =>
variant === "paths"
? Effect.sync(() => {
initialScanTimedOut = true;
})
: Effect.fail(error),
}),
);

const hasIncompleteInitialScan = () => initialScanTimedOut && finder.isScanning();
Expand Down
19 changes: 10 additions & 9 deletions apps/server/src/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2697,15 +2697,16 @@ const layerWsRpc = (
status,
}),
),
Effect.catchTag("RelayClientInstallError", (error) =>
Queue.fail(
queue,
new RelayClientInstallFailedError({
reason: error.reason,
message: error.message,
}),
),
),
Effect.catchTags({
RelayClientInstallError: (error) =>
Queue.fail(
queue,
new RelayClientInstallFailedError({
reason: error.reason,
message: error.message,
}),
),
}),
Effect.andThen(Queue.end(queue)),
Effect.forkScoped,
),
Expand Down
2 changes: 2 additions & 0 deletions oxlint-plugin-t3code/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import noMobileUniwindThemeEscapeHatches from "./rules/no-mobile-uniwind-theme-e
import noNativeTitleTooltip from "./rules/no-native-title-tooltip.ts";
import noTestInLoop from "./rules/no-test-in-loop.ts";
import noUnscopedHas from "./rules/no-unscoped-has.ts";
import preferCatchTags from "./rules/prefer-catch-tags.ts";
import requireSuppressionReason from "./rules/require-suppression-reason.ts";

export default definePlugin({
Expand All @@ -25,6 +26,7 @@ export default definePlugin({
"no-native-title-tooltip": noNativeTitleTooltip,
"no-test-in-loop": noTestInLoop,
"no-unscoped-has": noUnscopedHas,
"prefer-catch-tags": preferCatchTags,
"require-suppression-reason": requireSuppressionReason,
},
});
64 changes: 64 additions & 0 deletions oxlint-plugin-t3code/rules/prefer-catch-tags.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { assert, describe } from "@effect/vitest";

import { createOxlintRuleHarness } from "../test/utils.ts";

const rule = createOxlintRuleHarness("t3code/prefer-catch-tags");

describe("t3code/prefer-catch-tags", () => {
rule.valid(
"allows Effect.catchTags",
`
import * as Effect from "effect/Effect";

export const program = Effect.fail({ _tag: "A" } as const).pipe(
Effect.catchTags({ A: () => Effect.void }),
);
`,
);

rule.valid(
"ignores catchTag on other modules",
`
import * as Stream from "effect/Stream";

export const recover = Stream.catchTag("A", () => Stream.empty);
`,
);

rule.valid(
"ignores a local that shadows the namespace",
`
import * as Effect from "effect/Effect";

export const program = Effect.void;
export function recover(Effect: { catchTag: (tag: string) => void }) {
Effect.catchTag("A");
}
`,
);

rule.invalid(
"reports Effect.catchTag",
`
import * as Effect from "effect/Effect";

export const program = Effect.fail({ _tag: "A" } as const).pipe(
Effect.catchTag("A", () => Effect.void),
);
`,
(output) => {
assert.match(output, /Effect\.catchTags\(\{ Tag: handler \}\)/);
},
);

rule.invalid(
"reports catchTag through an aliased namespace",
`
import * as Eff from "effect/Effect";

export const recover = Eff.catchTag("A", () => Eff.void);
`,
);

rule.invalid("reports a named catchTag import", `import { catchTag } from "effect/Effect";`);
});
Loading
Loading