Skip to content

[codex] Refactor client runtime Effect services#3200

Merged
juliusmarminge merged 4 commits into
mainfrom
codex/effect-service-client-runtime-relay
Jun 20, 2026
Merged

[codex] Refactor client runtime Effect services#3200
juliusmarminge merged 4 commits into
mainfrom
codex/effect-service-client-runtime-relay

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Jun 20, 2026

Copy link
Copy Markdown
Member

Summary

  • migrate the managed relay client and relay discovery services to inline Context.Service contracts with canonical make and layer exports
  • split managed-relay client and DPoP signer failures into concrete Schema.TaggedErrorClass branches, retaining category-level Schema.Union exports and structured request diagnostics
  • derive relay and connection-persistence error messages from structured attributes instead of storing free-form messages or multiplexing materially different failures behind reason fields
  • expose relay service modules as the Discovery and ManagedRelay namespaces and update web, mobile, and client-runtime consumers to use qualified members
  • expose connection composition as Connection.layer and give the concrete resolver canonical make and layer exports
  • add explicit layer/runtime composition types where generated declarations need them

Design notes

Layer.effect is not a requirement: each concrete service exports a canonical layer using the constructor appropriate to its implementation. The managed relay client remains a parameterized layer because its URL, public client ID, and optional token store are runtime inputs. Platform capability, persistence, connection-source, and DPoP signer tags remain abstract ports whose implementations and layers are platform-specific; no synthetic make or layer is added for those ports. Orchestration modules are intentionally excluded because they are being reworked separately in #2829.

Validation

  • client-runtime plus cloud web/mobile suites: 46 files, 284 tests passed
  • focused managed-relay error mapping: 1 file, 5 tests passed
  • vp check: passed with 0 errors and 20 existing repository warnings
  • vp run typecheck: passed
  • vp run lint:mobile: passed
  • service-boundary import audit: service namespaces use canonical module/service names; mixed helper modules use named imports
  • git diff --check: passed

Note

Refactor client-runtime relay exports to use ManagedRelay and Discovery namespaces

  • Changes packages/client-runtime/src/relay/index.ts to export managedRelay.ts and discovery.ts symbols under ManagedRelay.* and Discovery.* namespaces instead of flat top-level exports, requiring all consumers to update their import paths.
  • Replaces the single ManagedRelayClientError tagged error with a discriminated union of typed errors (ManagedRelayRequestFailedError, ManagedRelayRequestTimeoutError, ManagedRelayUrlInvalidError, ManagedRelayAccessTokenScopesUnexpectedError, ManagedRelayDpopKeyLoadError, etc.), each carrying structured context fields.
  • Updates mapManagedRelayError in packages/client-runtime/src/connection/errors.ts to classify relay errors into specific ConnectionAttemptError types by switching on error tags rather than inspecting causes generically.
  • DPoP signer errors now emit ManagedRelayDpopKeyLoadError and ManagedRelayDpopProofCreationError with keyStore, method, and url context instead of a single ManagedRelayDpopSignerError.
  • Behavioral Change: decodedRelayClientError in both mobile and web linkEnvironment.ts now only extracts relayError and traceId when the cause tag is ManagedRelayRequestFailedError; other error variants produce messages without relay-protected detail.

Macroscope summarized f3a36b4.


Note

Medium Risk
Touches relay auth, DPoP, token cache, and cloud link/connect paths across client-runtime, web, and mobile; error-tag changes alter how relay failures are classified and surfaced to users.

Overview
Refactors how client-runtime exposes relay services and how failures surface through the stack.

Relay module shape: packages/client-runtime/src/relay/index.ts now exports Discovery and ManagedRelay namespaces instead of flat symbols. Web, mobile, and tests import ManagedRelay.ManagedRelayClient, ManagedRelay.layer, ManagedRelay.ManagedRelayDpopSigner, and similar qualified members. Relay environment discovery atoms pull discovery state from the Discovery namespace.

Managed relay client API: The parameterized client layer is ManagedRelay.layer with a canonical make factory (replacing managedRelayClientLayer). Service types stay on inline Context.Service contracts.

Error model: The single ManagedRelayClientError wrapper is replaced by a discriminated union of Schema.TaggedErrorClass variants (ManagedRelayRequestFailedError, ManagedRelayRequestTimeoutError, ManagedRelayUrlInvalidError, DPoP key/proof errors, scope mismatch, etc.) with structured fields (activity, action, relayUrl, keyStore, …). Timeouts and invalid URLs are first-class tags rather than nested causes.

Downstream mapping: mapManagedRelayError switches on _tag to map timeouts, config, permission, and auth failures into connection attempt errors. Mobile/web decodedRelayClientError only attaches relayError and traceId when the cause is ManagedRelayRequestFailedError; other tags no longer expose relay-protected detail. Platform DPoP signer layers emit ManagedRelayDpopKeyLoadError / ManagedRelayDpopProofCreationError (with expo-secure-store vs indexed-db) instead of a generic signer error.

Typing: Mobile/web runtimeLayer and runtime gain explicit Layer / ManagedRuntime composition types where generated declarations need them.

Reviewed by Cursor Bugbot for commit f3a36b4. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 893221eb-ac7d-49dc-ac8a-c251599c05c4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/effect-service-client-runtime-relay

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Jun 20, 2026
@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

🚀 Expo continuous deployment is ready!

  • Project → t3-code
  • Platforms → android, ios
  • Scheme → t3code-preview
  🤖 Android 🍎 iOS
Fingerprint fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea ae3bd597809dfd7771d0898f735d172973d4c1c8
Build Details Build Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
App version: 0.1.0
Git commit: 92eee17a1337fb6a188937c323e76dae2c386585
Build Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
App version: 0.1.0
Git commit: eb448c5d21d67cc2c283172b1ea3495232588935
Update Details Update Permalink
DetailsBranch: pr-3200
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
Git commit: 92eee17a1337fb6a188937c323e76dae2c386585
Update Permalink
DetailsBranch: pr-3200
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
Git commit: 92eee17a1337fb6a188937c323e76dae2c386585
Update QR

@juliusmarminge juliusmarminge marked this pull request as ready for review June 20, 2026 02:06
@macroscopeapp

macroscopeapp Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved

Mechanical refactoring that converts to namespace exports and restructures error types for better type safety. All changes update import patterns and type references without altering runtime behavior.

No code changes detected at f3a36b4. Prior analysis still applies.

You can customize Macroscope's approvability policy. Learn more.

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jun 20, 2026
@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch from c7e149a to 7b181b2 Compare June 20, 2026 02:15
@macroscopeapp macroscopeapp Bot dismissed their stale review June 20, 2026 02:15

Dismissing prior approval to re-evaluate 7b181b2

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jun 20, 2026
@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch from 7b181b2 to 9052877 Compare June 20, 2026 03:13
@macroscopeapp macroscopeapp Bot dismissed their stale review June 20, 2026 03:14

Dismissing prior approval to re-evaluate 9052877

@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Jun 20, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jun 20, 2026
@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch from 9052877 to 5aea238 Compare June 20, 2026 04:00
@macroscopeapp macroscopeapp Bot dismissed their stale review June 20, 2026 04:00

Dismissing prior approval to re-evaluate 5aea238

@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch from 5aea238 to 80378d3 Compare June 20, 2026 04:34
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jun 20, 2026
@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch from 80378d3 to 92ac971 Compare June 20, 2026 04:51
@macroscopeapp macroscopeapp Bot dismissed their stale review June 20, 2026 04:51

Dismissing prior approval to re-evaluate 92ac971

@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch 3 times, most recently from 721f98f to 5360201 Compare June 20, 2026 05:18

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Relay config errors mapped retryable
    • Replaced the default fallthrough branch in mapManagedRelayError with explicit case arms that map ManagedRelayUrlInvalidError to ConnectionBlockedError(configuration) and all DPoP/token errors to ConnectionBlockedError(authentication), preventing permanent failures from being treated as retryable transient errors.

Create PR

Or push these changes by commenting:

@cursor push 2ffc3bc303
Preview (2ffc3bc303)
diff --git a/apps/mobile/src/connection/runtime.ts b/apps/mobile/src/connection/runtime.ts
--- a/apps/mobile/src/connection/runtime.ts
+++ b/apps/mobile/src/connection/runtime.ts
@@ -1,4 +1,4 @@
-import { connectionLayer as clientConnectionLayer } from "@t3tools/client-runtime/connection";
+import { Connection } from "@t3tools/client-runtime/connection";
 import * as Layer from "effect/Layer";
 import { Atom } from "effect/unstable/reactivity";
 
@@ -9,8 +9,19 @@
   Layer.provide(runtimeContextLayer),
 );
 
-export const connectionLayer = clientConnectionLayer.pipe(
+type ConnectionLayerSource =
+  | typeof Connection.layer
+  | typeof runtimeContextLayer
+  | typeof providedConnectionPlatformLayer;
+
+export const connectionLayer: Layer.Layer<
+  Layer.Success<ConnectionLayerSource>,
+  Layer.Error<ConnectionLayerSource>
+> = Connection.layer.pipe(
   Layer.provideMerge(Layer.mergeAll(runtimeContextLayer, providedConnectionPlatformLayer)),
 );
 
-export const connectionAtomRuntime = Atom.runtime(connectionLayer);
+export const connectionAtomRuntime: Atom.AtomRuntime<
+  Layer.Success<typeof connectionLayer>,
+  Layer.Error<typeof connectionLayer>
+> = Atom.runtime(connectionLayer);

diff --git a/apps/mobile/src/connection/storage.ts b/apps/mobile/src/connection/storage.ts
--- a/apps/mobile/src/connection/storage.ts
+++ b/apps/mobile/src/connection/storage.ts
@@ -1,5 +1,6 @@
 import {
   ConnectionPersistenceError,
+  type ConnectionPersistenceOperation,
   ConnectionRegistrationStore,
   ConnectionTargetStore,
   EnvironmentCacheStore,
@@ -62,19 +63,10 @@
   });
 }
 
-function shellPersistenceError(
-  operation:
-    | "load-shell"
-    | "save-shell"
-    | "load-thread"
-    | "save-thread"
-    | "remove-thread"
-    | "clear-environment",
-  cause: unknown,
-) {
+function shellPersistenceError(operation: ConnectionPersistenceOperation, cause: unknown) {
   return new ConnectionPersistenceError({
     operation,
-    message: `Could not ${operation.replaceAll("-", " ")}: ${String(cause)}`,
+    cause,
   });
 }
 
@@ -118,12 +110,12 @@
 });
 
 function targetPersistenceError(
-  operation: "list-targets" | "register-connection" | "remove-connection",
+  operation: ConnectionPersistenceOperation,
   error: ConnectionTransientError,
 ) {
   return new ConnectionPersistenceError({
     operation,
-    message: error.message,
+    cause: error,
   });
 }
 

diff --git a/apps/mobile/src/features/agent-awareness/liveActivityPreferences.test.ts b/apps/mobile/src/features/agent-awareness/liveActivityPreferences.test.ts
--- a/apps/mobile/src/features/agent-awareness/liveActivityPreferences.test.ts
+++ b/apps/mobile/src/features/agent-awareness/liveActivityPreferences.test.ts
@@ -2,7 +2,7 @@
 import { describe, expect, it } from "@effect/vitest";
 import * as Effect from "effect/Effect";
 import type { EnvironmentId } from "@t3tools/contracts";
-import { ManagedRelayClient } from "@t3tools/client-runtime/relay";
+import { ManagedRelay } from "@t3tools/client-runtime/relay";
 import * as Layer from "effect/Layer";
 import { HttpClient } from "effect/unstable/http";
 
@@ -35,7 +35,7 @@
 };
 
 const testLayer = Layer.mergeAll(
-  Layer.succeed(ManagedRelayClient, null as never),
+  Layer.succeed(ManagedRelay.ManagedRelayClient, null as never),
   Layer.succeed(
     HttpClient.HttpClient,
     HttpClient.make(() => Effect.die("unexpected HTTP request")),

diff --git a/apps/mobile/src/features/agent-awareness/liveActivityPreferences.ts b/apps/mobile/src/features/agent-awareness/liveActivityPreferences.ts
--- a/apps/mobile/src/features/agent-awareness/liveActivityPreferences.ts
+++ b/apps/mobile/src/features/agent-awareness/liveActivityPreferences.ts
@@ -1,6 +1,6 @@
 import * as Effect from "effect/Effect";
 import { HttpClient } from "effect/unstable/http";
-import { ManagedRelayClient } from "@t3tools/client-runtime/relay";
+import { ManagedRelay } from "@t3tools/client-runtime/relay";
 
 import type { SavedRemoteConnection } from "../../lib/connection";
 import { savePreferencesPatch } from "../../lib/storage";
@@ -11,7 +11,7 @@
   readonly enabled: boolean;
   readonly clerkToken: string | null;
   readonly connections: ReadonlyArray<SavedRemoteConnection>;
-}): Effect.Effect<void, unknown, HttpClient.HttpClient | ManagedRelayClient> {
+}): Effect.Effect<void, unknown, HttpClient.HttpClient | ManagedRelay.ManagedRelayClient> {
   return Effect.gen(function* () {
     yield* Effect.tryPromise({
       try: () => savePreferencesPatch({ liveActivitiesEnabled: input.enabled }),

diff --git a/apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts b/apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts
--- a/apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts
+++ b/apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts
@@ -9,7 +9,7 @@
 import * as Exit from "effect/Exit";
 import * as Layer from "effect/Layer";
 import { FetchHttpClient } from "effect/unstable/http";
-import { type ManagedRelayClient } from "@t3tools/client-runtime/relay";
+import { ManagedRelay } from "@t3tools/client-runtime/relay";
 
 import type { EnvironmentId } from "@t3tools/contracts";
 import { verifyDpopProof } from "@t3tools/shared/dpop";
@@ -158,7 +158,7 @@
       }
       idlePasses = 0;
       const exit = yield* Effect.exit(
-        pending.operation as Effect.Effect<unknown, unknown, ManagedRelayClient>,
+        pending.operation as Effect.Effect<unknown, unknown, ManagedRelay.ManagedRelayClient>,
       );
       yield* Effect.sync(() => {
         pending.resolve(exit);

diff --git a/apps/mobile/src/features/agent-awareness/remoteRegistration.ts b/apps/mobile/src/features/agent-awareness/remoteRegistration.ts
--- a/apps/mobile/src/features/agent-awareness/remoteRegistration.ts
+++ b/apps/mobile/src/features/agent-awareness/remoteRegistration.ts
@@ -9,7 +9,7 @@
   type RelayLiveActivityRegistrationRequest,
 } from "@t3tools/contracts/relay";
 import { findErrorTraceId } from "@t3tools/client-runtime/errors";
-import { ManagedRelayClient } from "@t3tools/client-runtime/relay";
+import { ManagedRelay } from "@t3tools/client-runtime/relay";
 import {
   isAtomCommandInterrupted,
   settleAsyncResult,
@@ -175,7 +175,7 @@
 function registerDeviceWithRelay(
   body: RelayDeviceRegistrationRequest,
   expectedGeneration: number,
-): Effect.Effect<void, unknown, ManagedRelayClient> {
+): Effect.Effect<void, unknown, ManagedRelay.ManagedRelayClient> {
   return Effect.gen(function* () {
     if (expectedGeneration !== deviceRegistrationGeneration) {
       logRegistrationDebug("device registration cancelled before relay request", {
@@ -198,7 +198,7 @@
       return;
     }
 
-    const client = yield* ManagedRelayClient;
+    const client = yield* ManagedRelay.ManagedRelayClient;
     logRegistrationDebug("relay device registration request started", {
       expectedGeneration,
     });
@@ -215,7 +215,7 @@
 function unregisterDeviceWithRelay(input: {
   readonly deviceId: string;
   readonly tokenProvider: () => Promise<string | null>;
-}): Effect.Effect<void, unknown, ManagedRelayClient> {
+}): Effect.Effect<void, unknown, ManagedRelay.ManagedRelayClient> {
   return Effect.gen(function* () {
     if (!readRelayConfig()) return;
     const token = yield* Effect.tryPromise({
@@ -227,7 +227,7 @@
       return;
     }
 
-    const client = yield* ManagedRelayClient;
+    const client = yield* ManagedRelay.ManagedRelayClient;
     yield* client.unregisterDevice({
       clerkToken: token,
       deviceId: input.deviceId,
@@ -237,7 +237,7 @@
 
 function registerLiveActivityWithRelay(
   body: RelayLiveActivityRegistrationRequest,
-): Effect.Effect<boolean, unknown, ManagedRelayClient> {
+): Effect.Effect<boolean, unknown, ManagedRelay.ManagedRelayClient> {
   return Effect.gen(function* () {
     if (!readRelayConfig()) return false;
     const token = yield* relayToken;
@@ -246,7 +246,7 @@
       return false;
     }
 
-    const client = yield* ManagedRelayClient;
+    const client = yield* ManagedRelay.ManagedRelayClient;
     yield* client.registerLiveActivity({
       clerkToken: token,
       payload: body,
@@ -274,7 +274,7 @@
 }
 
 function runRegistrationInBackground(
-  operation: Effect.Effect<unknown, unknown, ManagedRelayClient>,
+  operation: Effect.Effect<unknown, unknown, ManagedRelay.ManagedRelayClient>,
   context: string,
 ): void {
   void (async () => {
@@ -370,7 +370,7 @@
 function registerDevice(
   input: DeviceRegistrationInput = {},
   expectedGeneration = deviceRegistrationGeneration,
-): Effect.Effect<void, unknown, ManagedRelayClient> {
+): Effect.Effect<void, unknown, ManagedRelay.ManagedRelayClient> {
   return Effect.gen(function* () {
     if (!canRegisterRemoteLiveActivities()) {
       logRegistrationDebug("device registration skipped; platform does not support it");
@@ -411,7 +411,7 @@
 
 function registerDeviceForCurrentUser(
   pushToStartToken?: string,
-): Effect.Effect<void, unknown, ManagedRelayClient> {
+): Effect.Effect<void, unknown, ManagedRelay.ManagedRelayClient> {
   return registerDevice(pushToStartToken ? { pushToStartToken } : undefined);
 }
 
@@ -485,7 +485,7 @@
 export function refreshAgentAwarenessRegistration(): Effect.Effect<
   void,
   never,
-  ManagedRelayClient
+  ManagedRelay.ManagedRelayClient
 > {
   return registerDeviceForCurrentUser().pipe(
     Effect.catch((error) =>
@@ -515,7 +515,7 @@
 
 export function unregisterAgentAwarenessDeviceForCurrentUser(
   tokenProvider: () => Promise<string | null>,
-): Effect.Effect<void, never, ManagedRelayClient> {
+): Effect.Effect<void, never, ManagedRelay.ManagedRelayClient> {
   return Effect.gen(function* () {
     const deviceId = yield* Effect.tryPromise({
       try: () => loadAgentAwarenessDeviceId(),
@@ -536,7 +536,7 @@
 
 export function registerLiveActivityPushToken(input: {
   readonly activity: LiveActivity<AgentActivityProps>;
-}): Effect.Effect<boolean, unknown, ManagedRelayClient> {
+}): Effect.Effect<boolean, unknown, ManagedRelay.ManagedRelayClient> {
   return Effect.gen(function* () {
     if (!canRegisterRemoteLiveActivities()) {
       return false;
@@ -588,7 +588,7 @@
 
 function registerLiveActivityPushTokenValue(input: {
   readonly activityPushToken: string;
-}): Effect.Effect<boolean, unknown, ManagedRelayClient> {
+}): Effect.Effect<boolean, unknown, ManagedRelay.ManagedRelayClient> {
   return Effect.gen(function* () {
     const deviceId = yield* Effect.tryPromise({
       try: () => loadOrCreateAgentAwarenessDeviceId(),
@@ -624,7 +624,7 @@
 export function refreshActiveLiveActivityRemoteRegistration(): Effect.Effect<
   void,
   never,
-  ManagedRelayClient
+  ManagedRelay.ManagedRelayClient
 > {
   return Effect.gen(function* () {
     if (!canRegisterRemoteLiveActivities() || !relayTokenProvider) {

diff --git a/apps/mobile/src/features/cloud/CloudAuthProvider.tsx b/apps/mobile/src/features/cloud/CloudAuthProvider.tsx
--- a/apps/mobile/src/features/cloud/CloudAuthProvider.tsx
+++ b/apps/mobile/src/features/cloud/CloudAuthProvider.tsx
@@ -1,6 +1,6 @@
 import { ClerkProvider, useAuth } from "@clerk/expo";
 import { tokenCache } from "@clerk/expo/token-cache";
-import { ManagedRelayClient, setManagedRelaySession } from "@t3tools/client-runtime/relay";
+import { ManagedRelay, setManagedRelaySession } from "@t3tools/client-runtime/relay";
 import {
   reportAtomCommandResult,
   settleAsyncResult,
@@ -22,7 +22,7 @@
 function resetManagedRelayTokenCache() {
   return settleAsyncResult(() =>
     runtime.runPromiseExit(
-      ManagedRelayClient.pipe(Effect.flatMap((client) => client.resetTokenCache)),
+      ManagedRelay.ManagedRelayClient.pipe(Effect.flatMap((client) => client.resetTokenCache)),
     ),
   );
 }

diff --git a/apps/mobile/src/features/cloud/linkEnvironment.test.ts b/apps/mobile/src/features/cloud/linkEnvironment.test.ts
--- a/apps/mobile/src/features/cloud/linkEnvironment.test.ts
+++ b/apps/mobile/src/features/cloud/linkEnvironment.test.ts
@@ -4,11 +4,7 @@
 import * as Layer from "effect/Layer";
 import { EnvironmentId } from "@t3tools/contracts";
 import { RelayMobileClientId } from "@t3tools/contracts/relay";
-import {
-  managedRelayClientLayer,
-  ManagedRelayClient,
-  ManagedRelayDpopSigner,
-} from "@t3tools/client-runtime/relay";
+import { ManagedRelay } from "@t3tools/client-runtime/relay";
 import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
 import { HttpClient } from "effect/unstable/http";
 
@@ -62,8 +58,8 @@
     Effect.succeed(`dpop:${input.method}:${input.url}`),
 );
 const testDpopSignerLayer = Layer.succeed(
-  ManagedRelayDpopSigner,
-  ManagedRelayDpopSigner.of({
+  ManagedRelay.ManagedRelayDpopSigner,
+  ManagedRelay.ManagedRelayDpopSigner.of({
     thumbprint: Effect.succeed("client-proof-key-thumbprint"),
     createProof: (input) => createProofMock(input),
   }),
@@ -73,7 +69,7 @@
   const httpClientLayer = remoteHttpClientLayer((input, init) => globalThis.fetch(input, init));
   return Layer.mergeAll(
     httpClientLayer,
-    managedRelayClientLayer({
+    ManagedRelay.layer({
       relayUrl: "https://relay.example.test",
       clientId: RelayMobileClientId,
     }).pipe(Layer.provideMerge(testDpopSignerLayer), Layer.provide(httpClientLayer)),
@@ -81,7 +77,11 @@
 }
 
 const withCloudServices = <A, E>(
-  effect: Effect.Effect<A, E, HttpClient.HttpClient | ManagedRelayClient | ManagedRelayDpopSigner>,
+  effect: Effect.Effect<
+    A,
+    E,
+    HttpClient.HttpClient | ManagedRelay.ManagedRelayClient | ManagedRelay.ManagedRelayDpopSigner
+  >,
 ) => effect.pipe(Effect.provide(cloudClientLayer()));
 
 function validLinkProof() {

diff --git a/apps/mobile/src/features/cloud/linkEnvironment.ts b/apps/mobile/src/features/cloud/linkEnvironment.ts
--- a/apps/mobile/src/features/cloud/linkEnvironment.ts
+++ b/apps/mobile/src/features/cloud/linkEnvironment.ts
@@ -25,11 +25,7 @@
 import { exchangeRemoteDpopAccessToken } from "@t3tools/client-runtime/authorization";
 import { fetchRemoteEnvironmentDescriptor } from "@t3tools/client-runtime/environment";
 import { findErrorTraceId } from "@t3tools/client-runtime/errors";
-import {
-  ManagedRelayClient,
-  type ManagedRelayClientError,
-  ManagedRelayDpopSigner,
-} from "@t3tools/client-runtime/relay";
+import { ManagedRelay } from "@t3tools/client-runtime/relay";
 import { makeEnvironmentHttpApiClient } from "@t3tools/client-runtime/rpc";
 
 import { authClientMetadata } from "../../lib/authClientMetadata";
@@ -156,13 +152,15 @@
 }
 
 function decodedRelayClientError(message: string) {
-  return (cause: ManagedRelayClientError) => {
-    const relayError = cause.relayError;
+  return (cause: ManagedRelay.ManagedRelayClientError) => {
+    const relayError =
+      cause._tag === "ManagedRelayRequestFailedError" ? cause.relayError : undefined;
+    const traceId = cause._tag === "ManagedRelayRequestFailedError" ? cause.traceId : undefined;
     const detail = relayError ? relayProtectedErrorMessage(relayError) : null;
     return new CloudEnvironmentLinkError({
       message: detail ? `${message}: ${detail}` : message,
       cause,
-      ...(cause.traceId ? { traceId: cause.traceId } : {}),
+      ...(traceId ? { traceId } : {}),
     });
   };
 }
@@ -261,7 +259,11 @@
 export function linkEnvironmentToCloud(input: {
   readonly connection: SavedRemoteConnection;
   readonly clerkToken: string;
-}): Effect.Effect<void, CloudEnvironmentLinkError, HttpClient.HttpClient | ManagedRelayClient> {
+}): Effect.Effect<
+  void,
+  CloudEnvironmentLinkError,
+  HttpClient.HttpClient | ManagedRelay.ManagedRelayClient
+> {
   return Effect.gen(function* () {
     if (!input.connection.bearerToken) {
       return yield* new CloudEnvironmentLinkError({
@@ -270,7 +272,7 @@
     }
     const localBearerToken = input.connection.bearerToken;
     const relayUrl = yield* requireRelayUrl();
-    const relayClient = yield* ManagedRelayClient;
+    const relayClient = yield* ManagedRelay.ManagedRelayClient;
     const deviceId = yield* Effect.tryPromise({
       try: () => loadOrCreateAgentAwarenessDeviceId(),
       catch: cloudEnvironmentLinkError("Could not load the mobile device id."),
@@ -353,11 +355,11 @@
 }): Effect.Effect<
   ReadonlyArray<RelayClientEnvironmentRecord>,
   CloudEnvironmentLinkError,
-  ManagedRelayClient
+  ManagedRelay.ManagedRelayClient
 > {
   return Effect.gen(function* () {
     const relayUrl = yield* requireRelayUrl();
-    const relayClient = yield* ManagedRelayClient;
+    const relayClient = yield* ManagedRelay.ManagedRelayClient;
 
     return yield* relayClient
       .listEnvironments({
@@ -374,11 +376,11 @@
 }): Effect.Effect<
   RelayEnvironmentStatusResponseType,
   CloudEnvironmentLinkError,
-  ManagedRelayClient
+  ManagedRelay.ManagedRelayClient
 > {
   return Effect.gen(function* () {
     const relayUrl = yield* requireRelayUrl();
-    const relayClient = yield* ManagedRelayClient;
+    const relayClient = yield* ManagedRelay.ManagedRelayClient;
     const status = yield* relayClient
       .getEnvironmentStatus({
         clerkToken: input.clerkToken,
@@ -413,7 +415,7 @@
 }): Effect.Effect<
   ReadonlyArray<CloudEnvironmentRecordWithStatus>,
   CloudEnvironmentLinkError,
-  ManagedRelayClient
+  ManagedRelay.ManagedRelayClient
 > {
   return Effect.forEach(
     input.environments,
@@ -445,7 +447,7 @@
 }): Effect.Effect<
   ReadonlyArray<CloudEnvironmentRecordWithStatus>,
   CloudEnvironmentLinkError,
-  ManagedRelayClient
+  ManagedRelay.ManagedRelayClient
 > {
   return Effect.gen(function* () {
     const environments = yield* listCloudEnvironments(input);
@@ -473,7 +475,7 @@
   }) {
     yield* Effect.annotateCurrentSpan({ "environment.id": input.environmentId });
     const relayUrl = yield* requireRelayUrl();
-    const relayClient = yield* ManagedRelayClient;
+    const relayClient = yield* ManagedRelay.ManagedRelayClient;
 
     const deviceId = yield* loadAgentAwarenessDeviceId();
     const connect = yield* relayClient
@@ -514,7 +516,7 @@
         message: "Connected endpoint descriptor does not match the selected environment.",
       });
     }
-    const signer = yield* ManagedRelayDpopSigner;
+    const signer = yield* ManagedRelay.ManagedRelayDpopSigner;
     const bootstrapDpop = yield* signer
       .createProof({
         method: "POST",
@@ -555,7 +557,7 @@
 }): Effect.Effect<
   SavedRemoteConnection,
   CloudEnvironmentLinkError,
-  HttpClient.HttpClient | ManagedRelayClient | ManagedRelayDpopSigner
+  HttpClient.HttpClient | ManagedRelay.ManagedRelayClient | ManagedRelay.ManagedRelayDpopSigner
 > {
   return connectRelayManagedEnvironment({
     clerkToken: input.clerkToken,
@@ -570,7 +572,7 @@
 }): Effect.Effect<
   SavedRemoteConnection,
   CloudEnvironmentLinkError,
-  HttpClient.HttpClient | ManagedRelayClient | ManagedRelayDpopSigner
+  HttpClient.HttpClient | ManagedRelay.ManagedRelayClient | ManagedRelay.ManagedRelayDpopSigner
 > {
   return connectRelayManagedEnvironment({
     clerkToken: input.clerkToken,

diff --git a/apps/mobile/src/features/cloud/managedRelayLayer.ts b/apps/mobile/src/features/cloud/managedRelayLayer.ts
--- a/apps/mobile/src/features/cloud/managedRelayLayer.ts
+++ b/apps/mobile/src/features/cloud/managedRelayLayer.ts
@@ -1,8 +1,4 @@
-import {
-  managedRelayClientLayer as makeManagedRelayClientLayer,
-  ManagedRelayDpopSigner,
-  ManagedRelayDpopSignerError,
-} from "@t3tools/client-runtime/relay";
+import { ManagedRelay } from "@t3tools/client-runtime/relay";
 import { RelayMobileClientId } from "@t3tools/contracts/relay";
 import * as Crypto from "effect/Crypto";
 import * as Effect from "effect/Effect";
@@ -12,16 +8,16 @@
 import { managedRelayAccessTokenStore } from "./managedRelayTokenStore";
 
 const relayDpopSignerLayer = Layer.effect(
-  ManagedRelayDpopSigner,
+  ManagedRelay.ManagedRelayDpopSigner,
   Effect.gen(function* () {
     const crypto = yield* Crypto.Crypto;
     const loadProofKey = yield* Effect.cached(
       loadOrCreateDpopProofKeyPair().pipe(Effect.provideService(Crypto.Crypto, crypto)),
     );
-    return ManagedRelayDpopSigner.of({
+    return ManagedRelay.ManagedRelayDpopSigner.of({
       thumbprint: loadProofKey.pipe(
         Effect.map((proofKey) => proofKey.thumbprint),
-        Effect.mapError((cause) => new ManagedRelayDpopSignerError({ cause })),
+        Effect.mapError((cause) => new ManagedRelay.ManagedRelayDpopKeyLoadError({ cause })),
         Effect.withSpan("mobile.managedRelayDpopSigner.loadThumbprint"),
       ),
       createProof: Effect.fn("mobile.managedRelayDpopSigner.createProof")(
@@ -32,14 +28,14 @@
             Effect.map((proof) => proof.proof),
           );
         },
-        Effect.mapError((cause) => new ManagedRelayDpopSignerError({ cause })),
+        Effect.mapError((cause) => new ManagedRelay.ManagedRelayDpopProofCreationError({ cause })),
       ),
     });
   }),
 );
 
 export const managedRelayClientLayer = (relayUrl: string) =>
-  makeManagedRelayClientLayer({
+  ManagedRelay.layer({
     relayUrl,
     clientId: RelayMobileClientId,
     accessTokenStore: managedRelayAccessTokenStore,

diff --git a/apps/mobile/src/features/cloud/managedRelayTokenStore.ts b/apps/mobile/src/features/cloud/managedRelayTokenStore.ts
--- a/apps/mobile/src/features/cloud/managedRelayTokenStore.ts
+++ b/apps/mobile/src/features/cloud/managedRelayTokenStore.ts
@@ -1,7 +1,4 @@
-import {
-  type ManagedRelayAccessTokenCacheEntry,
-  type ManagedRelayAccessTokenStore,
-} from "@t3tools/client-runtime/relay";
+import { ManagedRelay } from "@t3tools/client-runtime/relay";
 import * as Data from "effect/Data";
 import * as Effect from "effect/Effect";
 import * as Schema from "effect/Schema";
@@ -60,7 +57,7 @@
 }).pipe(
   Effect.flatMap((encoded) =>
     encoded === null
-      ? Effect.succeed<ReadonlyArray<ManagedRelayAccessTokenCacheEntry>>([])
+      ? Effect.succeed<ReadonlyArray<ManagedRelay.ManagedRelayAccessTokenCacheEntry>>([])
       : decodeManagedRelayAccessTokenCache(encoded).pipe(
           Effect.map((cache) => cache.entries),
           Effect.mapError(storeError("Persisted relay access tokens are invalid.")),
@@ -68,7 +65,9 @@
   ),
 );
 
-const saveManagedRelayAccessTokens = (entries: ReadonlyArray<ManagedRelayAccessTokenCacheEntry>) =>
+const saveManagedRelayAccessTokens = (
+  entries: ReadonlyArray<ManagedRelay.ManagedRelayAccessTokenCacheEntry>,
+) =>
   encodeManagedRelayAccessTokenCache({
     version: MANAGED_RELAY_TOKEN_CACHE_VERSION,
     entries,
@@ -87,7 +86,7 @@
   catch: storeError("Could not clear persisted relay access tokens."),
 });
 
-export const managedRelayAccessTokenStore: ManagedRelayAccessTokenStore = {
+export const managedRelayAccessTokenStore: ManagedRelay.ManagedRelayAccessTokenStore = {
   load: loadManagedRelayAccessTokens.pipe(
     Effect.tapError(logStoreFailure("load")),
     Effect.orElseSucceed(() => []),

diff --git a/apps/mobile/src/lib/runtime.ts b/apps/mobile/src/lib/runtime.ts
--- a/apps/mobile/src/lib/runtime.ts
+++ b/apps/mobile/src/lib/runtime.ts
@@ -15,7 +15,17 @@
 
 const httpClientLayer = remoteHttpClientLayer(fetch);
 
-export const runtimeLayer = Layer.merge(
+type RuntimeLayerSource =
+  | ReturnType<typeof managedRelayClientLayer>
+  | typeof Socket.layerWebSocketConstructorGlobal
+  | typeof cryptoLayer
+  | typeof httpClientLayer
+  | typeof tracingLayer;
+
+export const runtimeLayer: Layer.Layer<
+  Layer.Success<RuntimeLayerSource>,
+  Layer.Error<RuntimeLayerSource>
+> = Layer.merge(
   managedRelayClientLayer(configuredRelayUrl()),
   Socket.layerWebSocketConstructorGlobal,
 ).pipe(
@@ -24,6 +34,12 @@
   Layer.provideMerge(tracingLayer.pipe(Layer.provide(httpClientLayer))),
 );
 
-export const runtime = ManagedRuntime.make(runtimeLayer);
+export const runtime: ManagedRuntime.ManagedRuntime<
+  Layer.Success<typeof runtimeLayer>,
+  Layer.Error<typeof runtimeLayer>
+> = ManagedRuntime.make(runtimeLayer);
 
-export const runtimeContextLayer = Layer.effectContext(runtime.contextEffect);
+export const runtimeContextLayer: Layer.Layer<
+  Layer.Success<typeof runtimeLayer>,
+  Layer.Error<typeof runtimeLayer>
+> = Layer.effectContext(runtime.contextEffect);

diff --git a/apps/mobile/src/state/relay.ts b/apps/mobile/src/state/relay.ts
--- a/apps/mobile/src/state/relay.ts
+++ b/apps/mobile/src/state/relay.ts
@@ -2,5 +2,5 @@
 
 import { connectionAtomRuntime } from "../connection/runtime";
 
-export const relayEnvironmentDiscovery =
+export const relayEnvironmentDiscovery: ReturnType<typeof createRelayEnvironmentDiscoveryAtoms> =
   createRelayEnvironmentDiscoveryAtoms(connectionAtomRuntime);

diff --git a/apps/web/src/cloud/linkEnvironment.test.ts b/apps/web/src/cloud/linkEnvironment.test.ts
--- a/apps/web/src/cloud/linkEnvironment.test.ts
+++ b/apps/web/src/cloud/linkEnvironment.test.ts
@@ -23,11 +23,7 @@
 } from "@t3tools/client-runtime/connection";
 import { type RpcSession } from "@t3tools/client-runtime/rpc";
 import { EnvironmentRegistry } from "@t3tools/client-runtime/connection";
-import {
-  managedRelayClientLayer,
-  ManagedRelayClient,
-  ManagedRelayDpopSigner,
-} from "@t3tools/client-runtime/relay";
+import { ManagedRelay } from "@t3tools/client-runtime/relay";
 import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
 import { __resetDesktopPrimaryAuthForTests } from "../environments/primary/desktopAuth";
 
@@ -62,8 +58,8 @@
 
 const createProof = vi.fn(() => Effect.succeed("dpop-proof"));
 const dpopSignerLayer = Layer.succeed(
-  ManagedRelayDpopSigner,
-  ManagedRelayDpopSigner.of({
+  ManagedRelay.ManagedRelayDpopSigner,
+  ManagedRelay.ManagedRelayDpopSigner.of({
     thumbprint: Effect.succeed("thumbprint"),
     createProof,
   }),
@@ -73,7 +69,7 @@
   const http = remoteHttpClientLayer(globalThis.fetch);
   return Layer.mergeAll(
     http,
-    managedRelayClientLayer({
+    ManagedRelay.layer({
       relayUrl: "https://relay.example.test",
       clientId: RelayWebClientId,
     }).pipe(Layer.provideMerge(dpopSignerLayer), Layer.provide(http)),
@@ -131,7 +127,11 @@
 }
 
 function withServices<A, E>(
-  effect: Effect.Effect<A, E, HttpClient.HttpClient | ManagedRelayClient | EnvironmentRegistry>,
+  effect: Effect.Effect<
+    A,
+    E,
+    HttpClient.HttpClient | ManagedRelay.ManagedRelayClient | EnvironmentRegistry
+  >,
   options?: Parameters<typeof registryLayer>[0],
 ) {
   return effect.pipe(Effect.provide(services(options)));

diff --git a/apps/web/src/cloud/linkEnvironment.ts b/apps/web/src/cloud/linkEnvironment.ts
--- a/apps/web/src/cloud/linkEnvironment.ts
+++ b/apps/web/src/cloud/linkEnvironment.ts
@@ -25,7 +25,7 @@
 import { EnvironmentRegistry } from "@t3tools/client-runtime/connection";
 import { request, runStream } from "@t3tools/client-runtime/rpc";
 import { makeEnvironmentHttpApiClient } from "@t3tools/client-runtime/rpc";
-import { ManagedRelayClient, type ManagedRelayClientError } from "@t3tools/client-runtime/relay";
+import { ManagedRelay } from "@t3tools/client-runtime/relay";
 
 import {
   readPrimaryEnvironmentDescriptor,
@@ -164,13 +164,15 @@
 }
 
 function decodedRelayClientError(message: string) {
-  return (cause: ManagedRelayClientError) => {
-    const relayError = cause.relayError;
+  return (cause: ManagedRelay.ManagedRelayClientError) => {
+    const relayError =
+      cause._tag === "ManagedRelayRequestFailedError" ? cause.relayError : undefined;
+    const traceId = cause._tag === "ManagedRelayRequestFailedError" ? cause.traceId : undefined;
     const detail = relayError ? relayProtectedErrorMessage(relayError) : null;
     return new CloudEnvironmentLinkError({
       message: detail ? `${message}: ${detail}` : message,
       cause,
-      ...(cause.traceId ? { traceId: cause.traceId } : {}),
+      ...(traceId ? { traceId } : {}),
     });
   };
 }
@@ -268,7 +270,7 @@
 }): Effect.Effect<
   ReadonlyArray<RelayClientEnvironmentRecord>,
   CloudEnvironmentLinkError,
-  ManagedRelayClient
+  ManagedRelay.ManagedRelayClient
 > {
   return Effect.gen(function* () {
     const configuredRelayUrl = relayUrl();
@@ -277,7 +279,7 @@
         message: "T3CODE_RELAY_URL is not configured.",
       });
     }
-    const relayClient = yield* ManagedRelayClient;
+    const relayClient = yield* ManagedRelay.ManagedRelayClient;
     return yield* relayClient
       .listEnvironments({
         clerkToken: input.clerkToken,
@@ -299,7 +301,7 @@
 }): Effect.Effect<
   ReadonlyArray<RelayClientDeviceRecord>,
   CloudEnvironmentLinkError,
-  ManagedRelayClient
+  ManagedRelay.ManagedRelayClient
 > {
   return Effect.gen(function* () {
     if (!relayUrl()) {
@@ -307,7 +309,7 @@
         message: "T3CODE_RELAY_URL is not configured.",
       });
     }
-    const relayClient = yield* ManagedRelayClient;
+    const relayClient = yield* ManagedRelay.ManagedRelayClient;
     return yield* relayClient.listDevices({ clerkToken: input.clerkToken }).pipe(
       Effect.mapError(
         (cause) =>
@@ -351,7 +353,11 @@
 export function unlinkPrimaryEnvironmentFromCloud(input: {
   readonly target: CloudLinkTarget;
   readonly clerkToken: string | null;
-}): Effect.Effect<void, CloudEnvironmentLinkError, HttpClient.HttpClient | ManagedRelayClient> {
+}): Effect.Effect<
+  void,
+  CloudEnvironmentLinkError,
+  HttpClient.HttpClient | ManagedRelay.ManagedRelayClient
+> {
   return Effect.gen(function* () {
     const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);
     yield* client.connect
@@ -360,7 +366,7 @@
 
     const configuredRelayUrl = relayUrl();
     if (configuredRelayUrl && input.clerkToken) {
-      const relayClient = yield* ManagedRelayClient;
+      const relayClient = yield* ManagedRelay.ManagedRelayClient;
       yield* relayClient
         .unlinkEnvironment({
           clerkToken: input.clerkToken,
@@ -383,7 +389,7 @@
 }): Effect.Effect<
   void,
   CloudEnvironmentLinkError,
-  EnvironmentRegistry | HttpClient.HttpClient | ManagedRelayClient
+  EnvironmentRegistry | HttpClient.HttpClient | ManagedRelay.ManagedRelayClient
 > {
   return Effect.gen(function* () {
     const configuredRelayUrl = relayUrl();
@@ -392,7 +398,7 @@
         message: "T3CODE_RELAY_URL is not configured.",
       });
     }
-    const relayClient = yield* ManagedRelayClient;
+    const relayClient = yield* ManagedRelay.ManagedRelayClient;
     const environmentClient = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);
     yield* ensureRelayClientAvailable(EnvironmentId.make(input.target.environmentId));
 

diff --git a/apps/web/src/cloud/managedAuth.tsx b/apps/web/src/cloud/managedAuth.tsx
--- a/apps/web/src/cloud/managedAuth.tsx
+++ b/apps/web/src/cloud/managedAuth.tsx
@@ -1,5 +1,5 @@
 import { useAuth } from "@clerk/react";
... diff truncated: showing 800 of 4105 lines

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 5360201. Configure here.

Comment thread packages/client-runtime/src/connection/errors.ts
@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch from 5360201 to 5b314a7 Compare June 20, 2026 05:25
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jun 20, 2026
@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch from 5b314a7 to d3fe354 Compare June 20, 2026 05:47
@macroscopeapp macroscopeapp Bot dismissed their stale review June 20, 2026 05:47

Dismissing prior approval to re-evaluate d3fe354

@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch from d3fe354 to 119ebe3 Compare June 20, 2026 05:51
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jun 20, 2026
@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch from 119ebe3 to fdde198 Compare June 20, 2026 06:02
@macroscopeapp macroscopeapp Bot dismissed their stale review June 20, 2026 06:23

Dismissing prior approval to re-evaluate 604691c

@juliusmarminge juliusmarminge enabled auto-merge (squash) June 20, 2026 06:24
@juliusmarminge juliusmarminge disabled auto-merge June 20, 2026 06:24
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jun 20, 2026
@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch 2 times, most recently from bea99c6 to c0eceff Compare June 20, 2026 06:43
@macroscopeapp macroscopeapp Bot dismissed their stale review June 20, 2026 06:44

Dismissing prior approval to re-evaluate c0eceff

@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch 2 times, most recently from 1aa32d8 to 20301d4 Compare June 20, 2026 07:00
@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch from 20301d4 to 8a64adf Compare June 20, 2026 07:11
juliusmarminge and others added 4 commits June 20, 2026 00:14
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
@juliusmarminge juliusmarminge force-pushed the codex/effect-service-client-runtime-relay branch from 8a64adf to f3a36b4 Compare June 20, 2026 07:14
@juliusmarminge juliusmarminge enabled auto-merge (squash) June 20, 2026 07:15
@juliusmarminge juliusmarminge merged commit c00e721 into main Jun 20, 2026
16 checks passed
@juliusmarminge juliusmarminge deleted the codex/effect-service-client-runtime-relay branch June 20, 2026 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant