Skip to content

feat: upload composer images on attach

MacroscopeApp / Macroscope - Effect Service Conventions failed Aug 12, 2026 in 2m 54s

Effect Service Conventions: 2 issues found

Two error-modeling convention violations in the changed scope:

  • packages/contracts/src/assets.ts — new AttachmentUploadRequestError stores an unstructured detail string as its only attribute and derives message from it (no structural attributes, no cause). It is also never constructed; it appears only in the two new RPC error unions.
  • apps/server/src/orchestration/Normalizer.ts — the new fileSystem.stat and fileSystem.rename translations use Effect.mapError(() => new OrchestrationDispatchCommandError({ message })), discarding the underlying PlatformError even though the error accepts an optional cause.

Both were posted as inline review comments.

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.

Scope reviewed: new/changed Effect code in apps/server/src/assets/AttachmentUpload.ts, apps/server/src/attachmentStore.ts, apps/server/src/config.ts, apps/server/src/http.ts, apps/server/src/ws.ts, apps/server/src/orchestration/Normalizer.ts, apps/server/src/auth/RpcAuthorization.ts, packages/contracts/src/{assets,orchestration,rpc}.ts, plus the web/mobile call sites.

Checked and found compliant:

  • Imports: effect/* subpath namespace imports throughout (Clock, Effect, FileSystem, Option, Path, Schema); local service modules imported as namespaces (ServerConfig, ServerSecretStore, WorkspacePaths); named imports retained for @t3tools/contracts and pure helper modules, which is allowed.
  • Dependency acquisition: AttachmentUpload resolves ServerSecretStore.ServerSecretStore, ServerConfig.ServerConfig, FileSystem.FileSystem, and Path.Path from the environment rather than taking injected service instances; no module-global service closures, no ManagedRuntime.make, runPromise, or runPromiseExit added on the server side, and no Layer.succeed implementations wrapping runtime-backed APIs.
  • AttachmentUploadSigningKeyError has a semantically precise tag, a required real cause, and a static message derived from no opaque text — acceptable per the convention on Schema.Defect().
  • Effect.catch(() => Effect.void) in deletePendingAttachment intentionally handles the whole error channel, so catchTags is not required.
  • Error attributes/logs stay bounded: no signed URLs, tokens, or raw payloads copied into detail/message; the upload token itself is never logged.
  • Web-side attachmentUploadQueue.ts is an imperative UI-boundary module using the existing runAtomCommand helper and the zustand draft store, not an Effect service, so the runtime-boundary rules are not implicated.
  • No compatibility re-export shims or leftover service/layer files were introduced; new backend behavior is covered by focused tests (AttachmentUpload.test.ts, Normalizer.attachments.test.ts, attachmentStore.test.ts).

🔍 Agent activity — 1 tool call

💭 Looking at the diff for the Effect service conventions, I'm checking the new files being added and reviewing their import patterns to ensure they follow the established conventions. The key files include the attachment upload handler and updates to several core modules, and I'm verifying that the imports use proper namespace subpath conventions from the Effect library and allowed named imports from the contracts package. Now examining the error classes defined in the attachment upload module. The AttachmentUploadSigningKeyError looks fine — it has a precise tag and a static message without trying to stringify an opaque cause. However, AttachmentUploadRequestError is problematic because it stores an unstructured message as its only data field and derives the message directly from that field, which violates the convention that errors should have structured attributes with the message derived from those attributes rather than storing an unstructured string as the sole data. I need to check if AttachmentUploadRequestError is actually being constructed anywhere in the codebase, since I can see it declared in contracts and used in RPC error unions but haven't spotted where it's instantiated yet. Also noticing that attachmentStore.ts uses synchronous Node filesystem operations, which is a pre-existing pattern in that file — it's not a service, just pure helper functions, so it's probably acceptable even though it does bypass the FileSystem service abstraction. I need to check how AttachmentUploadRequestError is actually used in the codebase and compare it against similar error definitions like AssetAccessError to see if there's a pattern I'm missing. Also looking at the RPC handlers in ws.ts — attachmentsDelete chains through deletePendingAttachment, which has its own error channel from the requirements it needs, so I need to trace what errors it can actually produce. The key question is whether AttachmentUploadRequestError… I'm noticing that AttachmentUploadRequestErroris declared but never actually instantiated, and it only stores an unstructured message string… I'm noticing thatAttachmentUploadRequestErroronly has adetailfield with an unstructured message, which violates the convention of using structured attributes instead of storing data as a single message string. It's also declared in two RPC error unions but never actually constructed anywhere, making it a declared-but-unused error that doesn't follow the repo's patterns.… I'm noticing that the new error class usesdetailas a simple string, which goes against the established pattern of usingSchema.TaggedErrorClasswith structured attributes that derive the message from those attributes. The instructions are clear that this is the authoritative approach, even though the legacy codebase has plenty ofdetail: Schema.Stringpatterns. So this does seem like a violation of the stated convention, though I'm still considering whether it qualifies as a "clear" one since it's a newly introduced error class. Let me move on to check other parts of the code for potentially stronger findings. Looking at the client-side attachment upload queue module, it usesrunAtomCommandand imports from the client runtime, so it's not an Effect service itself. The module maintains some global state with a job map, but that's different from the convention about hiding dependencies in module globals—that rule applies specifically to Effect service construction. The queue is just a plain imperative module, though I should verify whether the runtime execution boundaries are being handled correctly. I'm also noticing that the state modules usecreateEnvironmentRpcCommandwith the connection atom runtime, which seems consistent with existing… The web side uses a client-runtime/zustand/atom setup rather than Effect services, so callingrunAtomCommandfrom a plain module isn't a convention violation. I'm checking whetherrunAtomCommandusesrunPromiseExitinternally, but that's in client-runtime and unchanged, so it's fine. Now looking at the Normalizer.ts changes in the orchestration layer, which uses fileSystem.stat and maps errors to OrchestrationDispatchCommandError.… I'm noticing that the error mapping in Normalizer.ts is dropping the underlying PlatformError when wrapping failures fromfileSystem.statandfileSystem.renamecalls, but the convention is to preserve the original error as thecausefield so the full error chain stays intact. I need to check whether the previous code was actually following this pattern or if this is a new issue. Looking at the old code, it was also dropping cause in the same way withfileSystem.makeDirectory, so this is a retained pattern rather than something newly introduced. Since these mapError calls are in changed lines within the PR scope, they're worth flagging as convention violations. I'll focus on the stat mapError around lines 122-129 and mention the rename case as well. Now I'm moving to the AttachmentUploadRequestErrorfinding—this is an unstructured error that only stores a detail message without deriving a proper message field from its attributes, which violates the convention about how error messages should be constructed. I'm also checking for any hidden runtime or dependency issues by scanning forLayer.succeed, ManagedRuntime, and runPromisepatterns in the changed files. The web queue usesrunAtomCommandwhich is an existing app-boundary helper, andawaitAttachmentUploadsreturns Promises, so that's fine. The server config's sync filesystem calls are probably out of scope since the instructions focus on service definitions and error handling rather than sync vs. async FS choices. Looking atAttachmentUpload.ts, the loadSigningSecretEffect correctly yieldsServerSecretStorefrom the environment, so the dependency injection is proper. ThestoreAttachmentUploadfunction returns a result union with HTTP status codes, and while it usesEffect.orElseSucceedto convert typed errors intook: false, that's more of a style choice than a convention violation. The new AttachmentUploadSigningKeyErrorfollows the same pattern asAssetSigningKeyLoadErrorwith a requiredcauseand static message, which is consistent. There's an unusedAttachmentUploadErrorexport that's minor, and I should verify whetherdeletePendingAttachment's error channel aligns with the RPC's declared AttachmentUploadRequestErrorunion, though that's not strictly required. I'm planning to post two inline comments: one aboutAttachmentUploadRequestErrorstoring an unstructureddetailstring without being constructed anywhere, suggesting it should use structural fields likeattachmentIdand a reason/operation literal instead, with the message derived from those fields. The second comment targets the error mapping inNormalizer.tswheremapErrordiscards the underlyingPlatformErroreven thoughcauseis optional on the error object — I should suggest passing the cause through. Now I need to map these comments to the actual diff line numbers on the right side, sinceassets.tslines 78-87 are new additions and should be commentable, while inNormalizer.tsline 126 is a changed line where the message was updated. Since I have two solid findings, I'll mark this as a failure state per the convention. For the first comment, an inline suggestion won't work cleanly since the fix isn't self-contained, so I'll provide a diff-less suggestion instead. I'm considering whether to add structural fields likeattachmentIdandreasonto the error class, but I'm realizing that using a reason literal to drive the user-facing message might violate the convention of splitting semantically distinct failures into separate classes. I should just describe the structural approach rather than suggest specific code. For the second comment, I'm looking at how to handle the file system stat call with proper error mapping that constructs anOrchestrationDispatchCommandError` with a descriptive message about the missing attachment.…

1. modify_pr — Effect service conventions review of the changed server/contracts code. Two findings on error modeling; the new AttachmentUpload module itself follows the repo's service/DI conventions (namespace subpath imports, dependencies acquired via yield* Foo.Foo, no hidden runtimes).


Agent Credits: 167 credits