Repository navigation
Effect Service Conventions: 1 issue found
1 finding
apps/mobile/src/connection/token-store.ts(L20-25):tokenErrorcopiesString(cause)into theConnectionTransientError.detailfield, which is also the caller-visiblemessage. Because the same helper wrapsencode/decodefailures ofTokenStore.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 intodetail.
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.tsapps/mobile/src/connection/catalog-store.tspackages/client-runtime/src/connection/supervisor.ts/supervisor.test.tspackages/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".MobileSecureStorageis imported as a namespace and accessed asMobileSecureStorage.MobileSecureStorage;@t3tools/contractskeeps named/type imports as allowed. - Dependency acquisition:
token-store.makeobtainsMobileSecureStoragefrom the environment; noManagedRuntime.make,runPromise, orrunPromiseExitwas introduced. Thecatalog: CatalogStoreparameter is not an Effect service (noContexttag exists for it) and mirrors the established composition-root pattern already used inapps/web/src/connection/storage.ts, where the catalog instance is created and shared inside the singleconnectionStorageLayer, so it was not treated as fake injection. - Service shape: the module owns real construction via
Effect.fn(...)-basedmake; the published tag remainsTokenStore.RemoteDpopAccessTokenStore, added to the existingLayer.effectContextcontext, so no extralayerexport is expected here (same as the adjacentcatalog-store.ts). - Error handling:
Effect.catchis used where the entire error channel is intentionally handled; nocatchTag/catchIf-on-predicate usage was introduced.tokenErroris 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
makebut nolayersince the TokenStore instance gets composed into connectionStorageLayer elsewhere, which matches the pattern used in catalog-store.ts. However, there's atokenErrorhelper 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 wheretargetPersistenceErrorchanged 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 thetokenErrorhelper follows the same pattern ascatalogErrorfrom the existing codebase, but this approach has a flaw—it concatenates arbitrary error details without properly preserving the cause chain. The issue is thatConnectionTransientErrordoesn't actually have acausefield 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, theString(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'smakeaccepts 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. ThemakeCatalogStore(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 howtokenErrorusesString(cause)to populate the detail field. This mirrors the existingcatalogErrorpattern 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. ThetokenErrorhelper is embedding the stringified cause directly into thedetailfield, 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 puttingString(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 thedetailfield, only the underlying value should be preserved incause—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 theEffect.mapErrorsites 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