Skip to content

perf(mobile): speed up multi-environment reconnect on foreground

MacroscopeApp / Macroscope - Effect Service Conventions failed Jul 29, 2026 in 5m 13s

Effect Service Conventions: 1 issue found

1 finding

  • apps/mobile/src/connection/token-store.ts (L20-25): tokenError copies String(cause) into the ConnectionTransientError.detail field, which is also the caller-visible message. Because the same helper wraps encode/decode failures of TokenStore.RemoteDpopAccessToken, schema issue text that can include token material may be embedded in the error/log output. Convention: keep error attributes bounded and derived from stable structural context; preserve the underlying failure as a cause rather than stringifying it into detail.

An inline review comment with a suggested fix was posted.

Details

Note

Your check run agent prompt is: .macroscope/check-run-agents/effect-service-conventions.md
More information about how Check Run Agents work can be found in our Help Center.

Files reviewed (in scope)

  • apps/mobile/src/connection/token-store.ts (new)
  • apps/mobile/src/connection/token-store.test.ts (new)
  • apps/mobile/src/connection/storage.ts
  • apps/mobile/src/connection/catalog-store.ts
  • packages/client-runtime/src/connection/supervisor.ts / supervisor.test.ts
  • packages/client-runtime/src/relay/managedRelay.ts / managedRelay.test.ts

Checks that passed

  • Imports: all Effect modules are imported as subpath namespaces (effect/Effect, effect/Ref, effect/Semaphore, ...); no consolidated named imports from "effect". MobileSecureStorage is imported as a namespace and accessed as MobileSecureStorage.MobileSecureStorage; @t3tools/contracts keeps named/type imports as allowed.
  • Dependency acquisition: token-store.make obtains MobileSecureStorage from the environment; no ManagedRuntime.make, runPromise, or runPromiseExit was introduced. The catalog: CatalogStore parameter is not an Effect service (no Context tag exists for it) and mirrors the established composition-root pattern already used in apps/web/src/connection/storage.ts, where the catalog instance is created and shared inside the single connectionStorageLayer, so it was not treated as fake injection.
  • Service shape: the module owns real construction via Effect.fn(...)-based make; the published tag remains TokenStore.RemoteDpopAccessTokenStore, added to the existing Layer.effectContext context, so no extra layer export is expected here (same as the adjacent catalog-store.ts).
  • Error handling: Effect.catch is used where the entire error channel is intentionally handled; no catchTag/catchIf-on-predicate usage was introduced. tokenError is reused across several operations and performs real normalization, so it is not flagged as a trivial constructor alias.
  • Tests: the added tests cover genuine behavior changes (probe timeout, foreground backoff reset, token-store split/migration/serialization, relay token-cache fast path), not a mechanical refactor.

Note

The flagged pattern is copied from the pre-existing catalogError helper in catalog-store.ts; only the newly introduced occurrence in token-store.ts was reported, per the instruction to treat the conventions as authoritative for changed scope without demanding unrelated legacy cleanup.


🔍 Agent activity — 1 tool call

💭 Looking at the new token-store.ts file to verify it follows the established conventions—checking the import patterns, like how TokenStore is imported from the authorization subpath and how MobileSecureStorage is imported and used. The module exports make but no layer since the TokenStore instance gets composed into connectionStorageLayer elsewhere, which matches the pattern used in catalog-store.ts. However, there's a tokenError helper that just wraps error construction, which violates the convention against adding helpers whose only purpose is instantiating an error object with passed arguments. The helper also stringifies the cause into the detail field as "Could not ${operation} ... : ${String(cause)}", which violates the rule against copying arbitrary defect text into detail — the cause should be preserved exactly as-is. The reason field uses "remote-unavailable" for all operations (encode, decode, save, load, delete), which seems overly generic. I need to check the ConnectionTransientError definition to see if it has a cause field, and also look at the storage.ts changes where targetPersistenceError changed its signature and error type from ConnectionTransientError to ConnectionAttemptError. The Effect.catch usage for handling decode errors looks appropriate since it's handling a single error channel intentionally. Looking at the ConnectionTransientError definition in model.ts. I'm noticing that the tokenError helper follows the same pattern as catalogError from the existing codebase, but this approach has a flaw—it concatenates arbitrary error details without properly preserving the cause chain. The issue is that ConnectionTransientError doesn't actually have a cause field defined in its schema, so this pattern doesn't quite fit. The real tension here is that flagging this as a convention violation would require modifying a shared error class in client-runtime, which is outside the scope of the current changes. The helper does add reusable context and normalization across six-plus call sites, so it's not just a trivial wrapper. That said, the String(cause) into detail directly violates the convention against copying arbitrary defect text, making it the strongest candidate for a real issue—even if the practical impact is limited by the scope constraints. Now checking the other files: managedRelay.ts looks fine with its fast path optimization, supervisor.ts has behavior changes backed by tests, and token-store.ts uses a closure-scoped semaphore map rather than a hidden module-level dependency. The bigger question is whether CatalogStore counts as an Effect service dependency that should be acquired from the environment rather than passed as a parameter—it's a plain interface with Effect-returning methods, but it's not registered as a Context.Service tag. The convention would flag this: token-store's make accepts CatalogStore as an implementation dependency parameter instead of yielding it from the environment. The catalog store is shared across multiple stores and needs to maintain the same instance for locking and caching, which would normally suggest making it a proper Context service. However, since there's no tag defined and the interface was just exported to enable this pattern, it's worth questioning whether this is genuinely problematic or just a pragmatic choice for sharing state. Looking at the repo's existing patterns, passing effect-returning objects to constructors is standard practice—TokenStore and ProfileStore both do this. So the question becomes whether token-store's dependency on catalog is fundamentally different from those cases, or if it's just following the established pattern for composing services at the same layer. The makeCatalogStore(backend) pattern is already established in the web app for catalog composition within a single layer, so passing a local store here follows the same convention. Since CatalogStore isn't an Effect service with a tag, it's not a service dependency that needs flagging—that would be applying an overly strict interpretation of legacy conventions. For the error handling, I'm looking at how tokenError uses String(cause) to populate the detail field. This mirrors the existing catalogError pattern in the same directory, but the instructions say to treat them as authoritative when older code differs and only report violations introduced or retained in the pull request's changed scope. Since token-store.ts is a new file, this pattern is being introduced here. The rule is clear: don't copy arbitrary defect text into detail, reason, or message—preserve only the exact underlying value as cause. ConnectionTransientError doesn't have a cause field, so I need to check whether Schema.TaggedErrorClass in Effect v4 even accepts a cause parameter, and whether any construction sites are actually passing one. There's no way to preserve the cause without modifying the shared error schema, which is outside the scope of this change. Adding the cause to the detail string would mean either updating the ConnectionTransientError class or creating a custom error, both of which seem inconsistent with the adjacent module's approach. The concern about including arbitrary defect text is mainly around sensitive or unbounded data, but here we're dealing with a keychain or JSON decode failure—though the detail string could potentially expose token content or raw values from a failed decode. The fix would be to avoid stringifying the cause into the error detail for token operations, since that can leak token material—something like a bounded description instead. That loses some diagnostic information since ConnectionTransientError has no cause field to preserve it, but a reviewer would likely push back on including token-related decode or encode errors in the detail string. I'm leaning toward flagging this as a finding. Let me also verify the imports and structure of the new files match the repo conventions. The tokenError helper is embedding the stringified cause directly into the detail field, which could expose sensitive data like raw DPoP access tokens for encode/decode failures, and it's losing structural context by not capturing the environment ID or operation as separate fields. I should suggest keeping the detail bounded and properly typed instead. The fix would be something like templating the detail message with the operation name and logging the cause separately, but that trades away debug information that maintainers might need. I'm weighing whether to report this credential exposure issue or call it clean by the repo's existing conventions—the check does support "all clear" findings if nothing's wrong. Looking at whether Schema.encodeEffect on a fully-typed class would realistically fail in a way that exposes the token. Actually, a decode failure on a corrupted string could include the raw token value in the Effect Schema error message, which violates the convention that arbitrary defect text shouldn't leak into detail. The spec says to preserve only the underlying cause, which is exactly what putting String(cause) in detail does. This is a new file so it's in scope for feedback. I'll add an inline comment on the tokenError function in token-store.ts pointing out the issue and suggesting a minimal fix that keeps detail bounded while logging the cause separately. I'm deciding whether to mark this as a failure state. The violation is clear—credentials shouldn't go into the detail field, only the underlying value should be preserved in cause—so this warrants a failure. I'll add an inline review comment on lines 20-25 pointing this out. For the fix itself, I need to suggest keeping the detail bounded while surfacing the cause through logging or annotation at the call sites rather than embedding it directly, since the Effect.mapError sites can't log themselves. I'll target line 23 (the detail line) for the comment and recommend this approach as the cleaner path forward.

1. modify_pr — One convention finding in the new mobile token store: the error wrapper embeds a stringified cause in detail. Everything else (namespace imports, Effect.fn construction, dependency acquisition via MobileSecureStorage, focused behavior tests for the supervisor/relay/token changes) looks consistent with the conventions.


Agent Credits: 153 credits