[codex] Structure client cloud cryptography errors#3395
Closed
juliusmarminge wants to merge 2 commits into
Closed
Conversation
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Contributor
ApprovabilityVerdict: Approved This PR refactors error classes to use structured typed operations instead of free-form string messages. The changes are purely structural with no runtime behavior modifications - errors are thrown at the same points with equivalent semantics. You can customize Macroscope's approvability policy. Learn more. |
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary\n\n- replace message-bearing DPoP errors with Schema errors carrying structured operation context\n- preserve exact lower-level causes and add typed key-format/mismatch causes for validation failures\n- remove valueless error constructor wrappers from web/mobile DPoP and mobile relay token storage\n- annotate best-effort token-store logging with the failing operation\n\n## Validation\n\n- WARN Unsupported engine: wanted: {"node":"^24.13.1"} (current: {"node":"v25.8.2","pnpm":"10.24.0"})
RUN /Users/julius/.codex/worktrees/client-cloud-crypto-errors
Test Files 3 passed (3)
;Tests 10 passed (10)
Start at 08:11:56
Duration 376ms (transform 78ms, setup 0ms, import 632ms, tests 86ms, environment 0ms)\n- WARN Unsupported engine: wanted: {"node":"^24.13.1"} (current: {"node":"v25.8.2","pnpm":"10.24.0"})
�[1m�[94mpass:�[39m�[0m All 1848 files are correctly formatted �[2m(775ms, 16 threads)�[0m
! react(no-unstable-nested-components): Do not define components during render.
,-[apps/web/src/components/ChatMarkdown.tsx:1250:8]
1249 | () => ({
1250 | ,-> p({ node: _node, children, ...props }) {
1251 | | return <p {...props}>{renderSkillInlineMarkdownChildren(children, skills)}
1252 |
-> }, 1253 | li({ node, children, ...props }) {----help: Move this component definition out of the parent component
ChatMarkdownand pass data as props. If you want to allow component creation in props, setallowAsPropsoption to true.! react(no-unstable-nested-components): Do not define components during render.
,-[apps/web/src/components/ChatMarkdown.tsx:1253:9]
1252 | },
1253 | ,-> li({ node, children, ...props }) {
1254 | | const listItemStart = node?.position?.start.offset;
1255 | | const markerOffset =
1256 | | typeof listItemStart === "number" ? findTaskListMarkerOffset(text, listItemStart) : null;
1257 | | return (
1258 | | <li {...props} data-task-marker-offset={markerOffset ?? undefined}>
1259 | | {renderSkillInlineMarkdownChildren(children, skills)}
1260 | |
1261 | | );
1262 |
-> }, 1263 | input({ node: _node, type, checked, disabled: _disabled, ...props }) {----help: Move this component definition out of the parent component
ChatMarkdownand pass data as props. If you want to allow component creation in props, setallowAsPropsoption to true.! react(no-unstable-nested-components): Do not define components during render.
,-[apps/web/src/components/ChatMarkdown.tsx:1263:12]
1262 | },
1263 | ,-> input({ node: _node, type, checked, disabled: _disabled, ...props }) {
1264 | | if (type !== "checkbox" || !onTaskListChange) {
1265 | | return (
1266 | | <input
1267 | | {...props}
1268 | | type={type}
1269 | | checked={checked}
1270 | | disabled={_disabled}
1271 | | readOnly={type === "checkbox"}
1272 | | />
1273 | | );
1274 | | }
1275 | | return (
1276 | | <input
1277 | | {...props}
1278 | | type="checkbox"
1279 | | name="markdown-task"
1280 | | aria-label="Toggle task"
1281 | | checked={checked}
1282 | | onChange={(event) => {
1283 | | const markerOffset = Number(
1284 | | event.currentTarget.closest("li")?.dataset.taskMarkerOffset,
1285 | | );
1286 | | if (!Number.isSafeInteger(markerOffset)) return;
1287 | | onTaskListChange({ markerOffset, checked: event.currentTarget.checked });
1288 | | }}
1289 | | />
1290 | | );
1291 |
-> }, 1292 | a({ node, href, children, ...props }) {----help: Move this component definition out of the parent component
ChatMarkdownand pass data as props. If you want to allow component creation in props, setallowAsPropsoption to true.! react(no-unstable-nested-components): Do not define components during render.
,-[apps/web/src/components/ChatMarkdown.tsx:1292:8]
1291 | },
1292 | ,-> a({ node, href, children, ...props }) {
1293 | | const normalizedHref = href ? normalizeMarkdownLinkHrefKey(href) : "";
1294 | | const fileLinkMeta = normalizedHref ? markdownFileLinkMetaByHref.get(normalizedHref) : null;
1295 | | if (!fileLinkMeta) {
1296 | | const faviconHost = resolveExternalLinkHost(href);
1297 | | const isSameDocumentLink = href?.startsWith("#") ?? false;
1298 | | const onClick = props.onClick;
1299 | | const canOpenInPreview = Boolean(threadRef) && isPreviewSupportedInRuntime();
1300 | | const link = (
1301 | | <a
1302 | | {...props}
1303 | | href={href}
1304 | | target={isSameDocumentLink ? undefined : "_blank"}
1305 | | rel={isSameDocumentLink ? undefined : "noopener noreferrer"}
1306 | | onClick={(event) => {
1307 | | onClick?.(event);
1308 | | if (isSameDocumentLink && href) {
1309 | | handleMarkdownFragmentClick(event, href);
1310 | | }
1311 | | }}
1312 | | onContextMenu={(event) => {
1313 | | if (!canOpenInPreview || !href) return;
1314 | | event.preventDefault();
1315 | | event.stopPropagation();
1316 | | const api = readLocalApi();
1317 | | if (!api) return;
1318 | | void api.contextMenu
1319 | | .show(
1320 | | [
1321 | | { id: "open-in-browser", label: "Open in integrated browser" },
1322 | | { id: "open-external", label: "Open in system browser" },
1323 | | ] as const,
1324 | | { x: event.clientX, y: event.clientY },
1325 | | )
1326 | | .then((clicked) => {
1327 | | if (clicked === "open-in-browser") {
1328 | | void openExternalLinkInPreview(href);
1329 | | return;
1330 | | }
1331 | | if (clicked === "open-external") return api.shell.openExternal(href);
1332 | | })
1333 | | .catch(() => undefined);
1334 | | }}
1335 | | >
1336 | | {faviconHost ? (
1337 | |
1338 | | {children}
1339 | |
1340 | | ) : (
1341 | | children
1342 | | )}
1343 | |
1344 | | );
1345 | | if (!faviconHost || !href) {
1346 | | return link;
1347 | | }
1348 | | return (
1349 | |
1350 | |
1351 | | <TooltipPopup
1352 | | side="top"
1353 | | className="max-w-[min(36rem,calc(100vw-2rem))] whitespace-normal leading-tight wrap-anywhere"
1354 | | >
1355 | | {href}
1356 | |
1357 | |
1358 | | );
1359 | | }
1360 | |
1361 | | const parentSuffix = fileLinkParentSuffixByPath.get(fileLinkMeta.filePath);
1362 | | const labelParts = [fileLinkMeta.basename];
1363 | | if (typeof parentSuffix === "string" && parentSuffix.length > 0) {
1364 | | labelParts.push(parentSuffix);
1365 | | }
1366 | | if (fileLinkMeta.line) {
1367 | | labelParts.push(
1368 | |
L${fileLinkMeta.line}${fileLinkMeta.column ?:C${fileLinkMeta.column}: ""},1369 | | );
1370 | | }
1371 | |
1372 | | return (
1373 | | <MarkdownFileLink
1374 | | href={fileLinkMeta.targetPath}
1375 | | targetPath={fileLinkMeta.targetPath}
1376 | | iconPath={fileLinkMeta.filePath}
1377 | | displayPath={fileLinkMeta.displayPath}
1378 | | workspaceRelativePath={fileLinkMeta.workspaceRelativePath}
1379 | | line={fileLinkMeta.line}
1380 | | label={labelParts.join(" · ")}
1381 | | copyMarkdown={
[${fileLinkMeta.basename}](${normalizedHref})}1382 | | theme={resolvedTheme}
1383 | | threadRef={threadRef}
1384 | | onOpen={openInPreferredEditor}
1385 | | onOpenInBrowser={
1386 | | threadRef &&
1387 | | isPreviewSupportedInRuntime() &&
1388 | | isBrowserPreviewFile(fileLinkMeta.filePath)
1389 | | ? () => openMarkdownFileInPreview(fileLinkMeta.filePath)
1390 | | : undefined
1391 | | }
1392 | | className={props.className}
1393 | | />
1394 | | );
1395 |
-> }, 1396 | table({ node: _node, ...props }) {----help: Move this component definition out of the parent component
ChatMarkdownand pass data as props. If you want to allow component creation in props, setallowAsPropsoption to true.! react(no-unstable-nested-components): Do not define components during render.
,-[apps/web/src/components/ChatMarkdown.tsx:1396:12]
1395 | },
1396 | ,-> table({ node: _node, ...props }) {
1397 | | return <MarkdownTable {...props} />;
1398 |
-> }, 1399 | details({ node: _node, children, open: detailsOpen }) {----help: Move this component definition out of the parent component
ChatMarkdownand pass data as props. If you want to allow component creation in props, setallowAsPropsoption to true.! react(no-unstable-nested-components): Do not define components during render.
,-[apps/web/src/components/ChatMarkdown.tsx:1399:14]
1398 | },
1399 | ,-> details({ node: _node, children, open: detailsOpen }) {
1400 | | return {children};
1401 |
-> }, 1402 | pre({ node, children, ...props }) {----help: Move this component definition out of the parent component
ChatMarkdownand pass data as props. If you want to allow component creation in props, setallowAsPropsoption to true.! react(no-unstable-nested-components): Do not define components during render.
,-[apps/web/src/components/ChatMarkdown.tsx:1402:10]
1401 | },
1402 | ,-> pre({ node, children, ...props }) {
1403 | | const codeBlock = extractCodeBlock(children);
1404 | | if (!codeBlock) {
1405 | | return <pre {...props}>{children};
1406 | | }
1407 | |
1408 | | const language = extractFenceLanguage(codeBlock.className);
1409 | | const fenceTitle = extractFenceTitle(extractPreCodeMeta(node));
1410 | | return (
1411 | | <MarkdownCodeBlock
1412 | | code={codeBlock.code}
1413 | | language={language}
1414 | | fenceTitle={fenceTitle}
1415 | | theme={resolvedTheme}
1416 | | >
1417 | | <CodeHighlightErrorBoundary fallback={<pre {...props}>{children}}>
1418 | | <Suspense fallback={<pre {...props}>{children}}>
1419 | | <SuspenseShikiCodeBlock
1420 | | className={codeBlock.className}
1421 | | code={codeBlock.code}
1422 | | themeName={diffThemeName}
1423 | | isStreaming={isStreaming}
1424 | | />
1425 | |
1426 | |
1427 | |
1428 | | );
1429 |
-> }, 1430 | }),----help: Move this component definition out of the parent component
ChatMarkdownand pass data as props. If you want to allow component creation in props, setallowAsPropsoption to true.! react(no-unstable-nested-components): Do not define components during render.
,-[apps/mobile/src/features/review/ReviewSheet.tsx:249:24]
248 | },
249 | ,-> headerTitle: () => (
250 | | <View style={{ alignItems: "center" }}>
251 | | <NativeText
252 | | numberOfLines={1}
253 | | style={{
254 | | fontFamily: "DMSans_700Bold",
255 | | fontSize: MOBILE_TYPOGRAPHY.headline.fontSize,
256 | | fontWeight: "900",
257 | | color: headerForeground,
258 | | letterSpacing: -0.4,
259 | | }}
260 | | >
261 | | Files Changed
262 | |
263 | | <View
264 | | style={{
265 | | flexDirection: "row",
266 | | alignItems: "center",
267 | | justifyContent: "center",
268 | | gap: 6,
269 | | flexWrap: "wrap",
270 | | }}
271 | | >
272 | | {headerDiffSummary.additions && headerDiffSummary.deletions ? (
273 | | <>
274 | | <NativeText
275 | | style={{
276 | | fontFamily: "DMSans_700Bold",
277 | | fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
278 | | fontWeight: "700",
279 | | color: "#16a34a",
280 | | }}
281 | | >
282 | | {headerDiffSummary.additions}
283 | |
284 | | <NativeText
285 | | style={{
286 | | fontFamily: "DMSans_700Bold",
287 | | fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
288 | | fontWeight: "700",
289 | | color: "#e11d48",
290 | | }}
291 | | >
292 | | {headerDiffSummary.deletions}
293 | |
294 | | {pendingReviewCommentCount > 0 ? (
295 | | <NativeText
296 | | style={{
297 | | fontFamily: "DMSans_700Bold",
298 | | fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
299 | | fontWeight: "700",
300 | | color: "#b45309",
301 | | }}
302 | | >
303 | | {pendingReviewCommentCount} pending
304 | |
305 | | ) : null}
306 | | </>
307 | | ) : (
308 | | <View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
309 | | <NativeText
310 | | numberOfLines={1}
311 | | style={{
312 | | fontFamily: "DMSans_700Bold",
313 | | fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
314 | | fontWeight: "700",
315 | | color: headerMuted,
316 | | }}
317 | | >
318 | | {selectedSection?.title ?? "Review changes"}
319 | |
320 | | {pendingReviewCommentCount > 0 ? (
321 | | <NativeText
322 | | style={{
323 | | fontFamily: "DMSans_700Bold",
324 | | fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
325 | | fontWeight: "700",
326 | | color: "#b45309",
327 | | }}
328 | | >
329 | | {pendingReviewCommentCount} pending
330 | |
331 | | ) : null}
332 | |
333 | | )}
334 | |
335 | |
336 |
-> ), 337 | }}----help: Move this component definition out of the parent component
ReviewSheetand pass data as props. If you want to allow component creation in props, setallowAsPropsoption to true.! react(no-unstable-nested-components): Do not define components during render.
,-[apps/mobile/src/features/threads/ThreadRouteScreen.tsx:383:24]
382 | headerBackTitle: "",
383 | ,-> headerTitle: () => (
384 | | <Pressable
385 | | style={{ alignItems: "center", maxWidth: 200 }}
386 | | onLongPress={() => {
387 | | // TODO: trigger rename modal
388 | | }}
389 | | >
390 | | <RNText
391 | | numberOfLines={1}
392 | | style={{
393 | | fontFamily: "DMSans_700Bold",
394 | | fontSize: MOBILE_TYPOGRAPHY.headline.fontSize,
395 | | fontWeight: "900",
396 | | color: foregroundColor,
397 | | letterSpacing: -0.4,
398 | | }}
399 | | >
400 | | {selectedThread.title}
401 | |
402 | | <RNText
403 | | numberOfLines={1}
404 | | style={{
405 | | fontFamily: "DMSans_700Bold",
406 | | fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
407 | | fontWeight: "700",
408 | | color: secondaryFg,
409 | | letterSpacing: 0.3,
410 | | }}
411 | | >
412 | | {headerSubtitle}
413 | |
414 | |
415 |
-> ), 416 | }}----help: Move this component definition out of the parent component
ThreadRouteScreenand pass data as props. If you want to allow component creation in props, setallowAsPropsoption to true.! react(no-unstable-nested-components): Do not define components during render.
,-[apps/web/src/components/CommandPalette.tsx:612:15]
611 | valuePrefix: "project",
612 | ,-> icon: (project) => (
613 | | <ProjectFavicon
614 | | environmentId={project.environmentId}
615 | | cwd={project.workspaceRoot}
616 | | className={ITEM_ICON_CLASS}
617 | | />
618 |
-> ), 619 | runProject: openProjectFromSearch,----help: Move this component definition out of the parent component
OpenCommandPaletteDialogand pass data as props. If you want to allow component creation in props, setallowAsPropsoption to true.! react(no-unstable-nested-components): Do not define components during render.
,-[apps/web/src/components/CommandPalette.tsx:630:15]
629 | shortcutCommand: "chat.new",
630 | ,-> icon: (project) => (
631 | | <ProjectFavicon
632 | | environmentId={project.environmentId}
633 | | cwd={project.workspaceRoot}
634 | | className={ITEM_ICON_CLASS}
635 | | />
636 |
-> ), 637 | runProject: async (project) => {----help: Move this component definition out of the parent component
OpenCommandPaletteDialogand pass data as props. If you want to allow component creation in props, setallowAsPropsoption to true.! react(no-unstable-nested-components): Do not define components during render.
,-[apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx:911:24]
910 | title: "",
911 | ,-> headerTitle: () => (
912 | | <View
913 | | style={{
914 | | alignItems: "center",
915 | | gap: 1,
916 | | maxWidth: 240,
917 | | }}
918 | | >
919 | | <RNText
920 | | numberOfLines={1}
921 | | style={{
922 | | color: terminalTheme.foreground,
923 | | fontFamily: "DMSans_700Bold",
924 | | fontSize: MOBILE_TYPOGRAPHY.footnote.fontSize,
925 | | lineHeight: 16,
926 | | }}
927 | | >
928 | | {headerTitle.topLine}
929 | |
930 | | <RNText
931 | | ellipsizeMode="middle"
932 | | numberOfLines={1}
933 | | style={{
934 | | color: terminalTheme.mutedForeground,
935 | | fontFamily: "Menlo",
936 | | fontSize: MOBILE_TYPOGRAPHY.caption.fontSize,
937 | | lineHeight: 14,
938 | | }}
939 | | >
940 | | {headerTitle.bottomLine}
941 | |
942 | |
943 |
-> ), 944 | }}----help: Move this component definition out of the parent component
ThreadTerminalRouteScreenand pass data as props. If you want to allow component creation in props, setallowAsPropsoption to true.! react(no-unstable-nested-components): Do not define components during render.
,-[apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx:492:24]
491 | headerShadowVisible: false,
492 | headerTitle: () => ,
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
493 | headerSearchBarOptions: {
---- help: Move this component definition out of the parent componentThreadFilesTreeScreenand pass data as props. If you want to allow component creation in props, setallowAsProps` option to true.! t3code(no-inline-schema-compile): Hoist Schema.decodeUnknownResult(...) to module scope: the compiled function is rebuilt on every call. Move it to a module-level const.
,-[apps/mobile/src/connection/storage.ts:293:15]
292 | const stored = yield* Effect.fromResult(
293 | Schema.decodeUnknownResult(StoredShellSnapshot)(parsed),
: ^^^^^^^^^^^^^^^^^^^^^^^^^^
294 | ).pipe(Effect.mapError((cause) => shellPersistenceError("load-shell", cause)));
`----
! t3code(no-inline-schema-compile): Hoist Schema.decodeUnknownResult(...) to module scope: the compiled function is rebuilt on every call. Move it to a module-level const.
,-[apps/mobile/src/connection/storage.ts:313:13]
312 | const legacyStored = yield* Effect.fromResult(
313 | Schema.decodeUnknownResult(LegacyStoredShellSnapshot)(legacyParsed),
: ^^^^^^^^^^^^^^^^^^^^^^^^^^
314 | ).pipe(Effect.mapError((cause) => shellPersistenceError("load-shell", cause)));
`----
! t3code(no-inline-schema-compile): Hoist Schema.encodeUnknownResult(...) to module scope: the compiled function is rebuilt on every call. Move it to a module-level const.
,-[apps/mobile/src/connection/storage.ts:328:13]
327 | const encoded = yield* Effect.fromResult(
328 | Schema.encodeUnknownResult(StoredShellSnapshot)(stored),
: ^^^^^^^^^^^^^^^^^^^^^^^^^^
329 | ).pipe(Effect.mapError((cause) => shellPersistenceError("save-shell", cause)));
`----
! t3code(no-inline-schema-compile): Hoist Schema.decodeUnknownResult(...) to module scope: the compiled function is rebuilt on every call. Move it to a module-level const.
,-[apps/mobile/src/connection/storage.ts:355:13]
354 | const stored = yield* Effect.fromResult(
355 | Schema.decodeUnknownResult(StoredThreadSnapshot)(parsed),
: ^^^^^^^^^^^^^^^^^^^^^^^^^^
356 | ).pipe(Effect.mapError((cause) => shellPersistenceError("load-thread", cause)));
`----
! t3code(no-inline-schema-compile): Hoist Schema.encodeUnknownResult(...) to module scope: the compiled function is rebuilt on every call. Move it to a module-level const.
,-[apps/mobile/src/connection/storage.ts:365:13]
364 | const encoded = yield* Effect.fromResult(
365 | Schema.encodeUnknownResult(StoredThreadSnapshot)({
: ^^^^^^^^^^^^^^^^^^^^^^^^^^
366 | schemaVersion: THREAD_SNAPSHOT_CACHE_SCHEMA_VERSION,
`----
! t3code(no-inline-schema-compile): Hoist Schema.decodeUnknownResult(...) to module scope: the compiled function is rebuilt on every call. Move it to a module-level const.
,-[apps/mobile/src/connection/catalog-store.ts:31:5]
30 | return yield* Effect.fromResult(
31 | Schema.decodeUnknownResult(ConnectionCatalogDocument)(parsed),
: ^^^^^^^^^^^^^^^^^^^^^^^^^^
32 | ).pipe(Effect.mapError((cause) => catalogError("decode", cause)));
`----
! t3code(no-inline-schema-compile): Hoist Schema.encodeUnknownResult(...) to module scope: the compiled function is rebuilt on every call. Move it to a module-level const.
,-[apps/mobile/src/connection/catalog-store.ts:39:5]
38 | const encoded = yield* Effect.fromResult(
39 | Schema.encodeUnknownResult(ConnectionCatalogDocument)(catalog),
: ^^^^^^^^^^^^^^^^^^^^^^^^^^
40 | ).pipe(Effect.mapError((cause) => catalogError("encode", cause)));
`----
Found 0 errors and 20 warnings in 1748 files (978ms, 16 threads)\n- WARN Unsupported engine: wanted: {"node":"^24.13.1"} (current: {"node":"v25.8.2","pnpm":"10.24.0"})
~/packages/contracts$ tsgo --noEmit ⊘ cache disabled
~/apps/marketing$ astro check ⊘ cache disabled
~/oxlint-plugin-t3code$ tsgo --noEmit ⊘ cache disabled
~/packages/effect-codex-app-server$ tsgo --noEmit ⊘ cache disabled
08:12:00 [WARN] [vite] warning:
optimizeDeps.esbuildOptionsoption was specified by "astro:dev-toolbar" plugin. This option is deprecated, please useoptimizeDeps.rolldownOptionsinstead.08:12:00 [WARN] [vite] You or a plugin you are using have set
optimizeDeps.esbuildOptionsbut this option is now deprecated. Vite now uses Rolldown to optimize the dependencies. Please useoptimizeDeps.rolldownOptionsinstead.08:12:00 [types] Generated 39ms
08:12:00 [check] Getting diagnostics for Astro files in /Users/julius/.codex/worktrees/client-cloud-crypto-errors/apps/marketing...
Result (10 files):
~/packages/effect-acp$ tsgo --noEmit ⊘ cache disabled
~/packages/shared$ tsgo --noEmit ⊘ cache disabled
~/packages/client-runtime$ tsgo --noEmit ⊘ cache disabled
~/scripts$ tsgo --noEmit ⊘ cache disabled
~/packages/ssh$ tsgo --noEmit ⊘ cache disabled
~/packages/tailscale$ tsgo --noEmit ⊘ cache disabled
~/apps/web$ tsgo --noEmit ⊘ cache disabled
~/apps/mobile$ tsc --noEmit ⊘ cache disabled
~/infra/relay$ tsgo --noEmit ⊘ cache disabled
~/apps/desktop$ tsgo --noEmit ⊘ cache disabled
~/apps/server$ tsgo --noEmit ⊘ cache disabled
vp run: 0/15 cache hit (0%). (Run
vp run --last-detailsfor full details)\n- WARN Unsupported engine: wanted: {"node":"^24.13.1"} (current: {"node":"v25.8.2","pnpm":"10.24.0"})$ node scripts/mobile-native-static-check.ts ⊘ cache disabled
Found 6 Swift and 2 Kotlin native source files.
$ swiftlint lint --config .swiftlint.yml --strict
$ ktlint modules/t3-terminal/android/src/main/java/expo/modules/t3terminal/T3TerminalModule.kt modules/t3-terminal/android/src/main/java/expo/modules/t3terminal/T3TerminalView.kt
$ detekt --config detekt.yml --input modules/t3-terminal/android/src/main/java/expo/modules/t3terminal/T3TerminalModule.kt,modules/t3-terminal/android/src/main/java/expo/modules/t3terminal/T3TerminalView.kt --build-upon-default-config
Skipping generated native project folders: android/, ios/.
Note
Medium Risk
Touches auth-related DPoP and secure token cache paths; error shape and message text change for any caller matching on strings, though signing/storage behavior is unchanged.
Overview
Replaces free-form
messagestrings on mobile/web DPoP and mobile managed relay token failures with Effect Schema tagged errors that carry a discriminatedoperationliteral and an optionalcause(Defect), plus derivedmessagegetters.On mobile DPoP, validation failures now use dedicated types (
DpopPublicKeyFormatError,DpopStoredPublicKeyMismatchError) wrapped asvalidate-key/derive-public-keywhere appropriate; invalid proof URLs fail asnormalize-proof-urlwithout a cause. Web DPoP follows the same pattern (includingisBrowserDpopErrorfor decode chaining).Managed relay token store errors are structured the same way; load/save/clear logging annotates the full error object and
operationinstead of a generic operation string. Tests assertoperation(and causes where relevant) rather than fixed message text.Reviewed by Cursor Bugbot for commit df4f4d7. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Structure cloud cryptography errors with operation-tagged
Schema.TaggedErrorClasstypesData.TaggedErrorwithSchema.TaggedErrorClassforCloudDpopError,BrowserDpopError, andManagedRelayTokenStoreError, adding anoperationliteral discriminant and optional typedcauseto each.messagegetter that formats the operation name, and new specific error types (DpopPublicKeyFormatError,DpopStoredPublicKeyMismatchError) are introduced for granular failure cases.isBrowserDpopErrorandisManagedRelayTokenStoreErrortype guards viaSchema.is.managedRelayTokenStorenow include the structured error object and its operation field instead of a freeform string.operationfield.Macroscope summarized df4f4d7.