Repository navigation
fix(server): preserve attachments referenced by surviving threads #23703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8fb5249
924d9a4
e6a27ca
1e4ccd7
34eb902
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5212,6 +5212,16 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> = | |
| FROM orchestration_v2_projection_messages AS message, | ||
| json_each(message.payload_json, '$.attachments') AS attachment | ||
| WHERE message.thread_id = ${threadId} | ||
| AND NOT EXISTS ( | ||
| SELECT 1 | ||
| FROM orchestration_v2_projection_threads AS remaining_thread | ||
| JOIN orchestration_v2_projection_messages AS remaining_message | ||
| ON remaining_message.thread_id = remaining_thread.thread_id, | ||
| json_each(remaining_message.payload_json, '$.attachments') AS remaining_attachment | ||
| WHERE remaining_thread.thread_id != ${threadId} | ||
| AND remaining_thread.deleted_at IS NULL | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fresh evidence in the current tree is AGENTS.md reference: AGENTS.md:L75-L75 Useful? React with 👍 / 👎. |
||
| AND json_extract(remaining_attachment.value, '$.id') = json_extract(attachment.value, '$.id') | ||
|
parmartejass marked this conversation as resolved.
Resolved
|
||
| ) | ||
| `, | ||
| sql<{ payload_json: string }>` | ||
| SELECT payload_json | ||
|
|
@@ -6810,12 +6820,26 @@ export const layerMemory: Layer.Layer<ProjectionStoreV2> = Layer.effect( | |
| state.projections.get(threadId)?.turnItems.find((item) => item.id === itemId) ?? null, | ||
| ), | ||
| ), | ||
| getThreadAttachmentIds: (threadId) => | ||
| service.getThreadProjection(threadId).pipe( | ||
| Effect.map((projection) => [ | ||
| getThreadAttachmentIds: Effect.fn("ProjectionStore.getThreadAttachmentIds")( | ||
| function* (threadId) { | ||
| const projection = yield* service.getThreadProjection(threadId); | ||
| const state = yield* Ref.get(replayState); | ||
| // Relaunched threads reuse uploads, including legacy ids without an ownership prefix. | ||
| const referencedIds = new Set( | ||
| Array.from(state.projections.values()).flatMap((other) => | ||
| other.thread.id !== threadId && other.thread.deletedAt === null | ||
| ? other.messages.flatMap((message) => | ||
| message.attachments.map((attachment) => attachment.id), | ||
| ) | ||
| : [], | ||
| ), | ||
| ); | ||
| return [ | ||
| ...new Set([ | ||
| ...projection.messages.flatMap((message) => | ||
| message.attachments.map((attachment) => attachment.id), | ||
| message.attachments | ||
| .map((attachment) => attachment.id) | ||
| .filter((id) => !referencedIds.has(id)), | ||
| ), | ||
| ...threadHtmlRenderAttachmentIds( | ||
| threadId, | ||
|
|
@@ -6824,8 +6848,9 @@ export const layerMemory: Layer.Layer<ProjectionStoreV2> = Layer.effect( | |
| ), | ||
| ), | ||
| ]), | ||
| ]), | ||
| ), | ||
| ]; | ||
| }, | ||
| ), | ||
| getThreadRecords: Effect.fn("ProjectionStore.getThreadRecords")( | ||
| function* (threadId, fields, filter) { | ||
| const projection = (yield* Ref.get(replayState)).projections.get(threadId); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the inspected mobile
Work locallypath,ThreadLaunchService.launchcreates the replacement thread and then commits amessage.dispatchcontaining the source attachment IDs, but this condition admits onlythread.deleteto the new semaphore. If deletion commits after the new thread is created but before its initial message, it sees no surviving reference and persists cleanup; the message can then commit, andResourceCleanupService.cleanupAttachmentsremoves the now-referenced file without another projection check. Make attachment-reference creation and cleanup atomic (or revalidate before removal), otherwise this multi-device ordering still loses uploads.AGENTS.md reference: AGENTS.md:L75-L75
Useful? React with 👍 / 👎.