Skip to content

fix(server): deleting a thread no longer removes another thread's attachments - #2

Open
parmartejass wants to merge 5 commits into
mainfrom
fix/thread-delete-foreign-attachments
Open

parmartejass wants to merge 5 commits into
mainfrom
fix/thread-delete-foreign-attachments

Conversation

@parmartejass

@parmartejass parmartejass commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Problem

Mobile “Work locally” can reuse a source thread's uploaded attachments. Deleting either thread previously scheduled those shared files for removal even while the other thread still referenced them.

Filtering by an attachment's thread prefix only protects deletion in one direction and excludes legacy IDs from cleanup.

Change

Both SQLite and in-memory projection stores now return message attachment IDs for cleanup only when no other non-deleted thread references them. Archived threads still protect their attachments; deleted threads no longer prevent cleanup when the last surviving thread is deleted. Legacy IDs follow the same reference checks, and the original legacy-deletion fixture is retained.

HTML-render ownership behavior is unchanged.

Scope and approval

This is a focused fix for an obvious data-loss bug in existing thread deletion. It qualifies for the small bug-fix exception in CONTRIBUTING.md and adds no product workflow or shared contract.

Verification

  • Four regression cases cover both deletion orders in SQLite and memory, including archived source threads, shared legacy IDs, prefixed IDs, and cleanup after the final reference disappears. All four fail against the original fork PR and pass with this revision.
  • vp test run apps/server/src/orchestration-v2/ProjectionStore.attachments.test.ts apps/server/src/orchestration-v2/TurnStartReads.test.ts apps/server/src/project/ProjectService.deletion.test.ts apps/server/src/attachmentStore.test.ts apps/server/src/orchestration-v2/ThreadDeletion.test.ts: 23 tests passed.
  • vp run check:changed upstream/main: format, lint, knip, GraphQL generation check, affected-package typecheck, and 38 adjacent tests passed.
  • Mobile flow was not tested end to end on a device.

Original implementation: Claude Code. Revised and verified with GPT-6 in Codex.

…achments

Thread deletion only kept html-render ids minted for the deleted thread;
message attachment ids were taken unfiltered. A thread whose messages
reuse another thread's uploads (mobile "Work locally" relaunch) deleted
the source's files. Both id kinds now share one ownership rule.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DrdVGEmdN2qsKpaCV9dBFT
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T16:13:14.417385Z 34eb902 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8fb52499b8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Only ids minted for another thread are excluded now. Ids without a
thread segment (pre-segment legacy data) are still cleaned up, and the
legacy deletion test is back on its original fixture.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DrdVGEmdN2qsKpaCV9dBFT

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 924d9a4cf0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/server/src/attachmentStore.ts Resolved
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M labels Oct 22, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6a27ca715

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +5221 to +5223
WHERE remaining_thread.thread_id != ${threadId}
AND remaining_thread.deleted_at IS NULL
AND json_extract(remaining_attachment.value, '$.id') = json_extract(attachment.value, '$.id')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make shared-attachment deletion atomic

When two live threads sharing an attachment are deleted concurrently—for example from separate clients or simultaneous forced project deletions—the per-thread locks allow both lookups to observe the other thread as live, so both omit the shared ID. Both deletions then commit cleanup effects without that ID, and no surviving thread remains to schedule its removal, leaking the file indefinitely; the reference check and deletion commit need serialization across attachment owners or an atomic/ref-counted cleanup decision.

AGENTS.md reference: AGENTS.md:L75-L75

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

The race is real but narrow. Two threads sharing a file have to be deleted at the same moment, by separate clients or two project deletes at once. Project delete already removes its threads one at a time. The result is one leftover file on disk, not lost data. A fix needs a lock or ref-count across all threads, which is more code than one stray file justifies. Not changing it here; leaving this open for the author.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Update: 34eb902 now serializes thread.delete through the orchestrator. One path remains: project deletion plans and commits thread deletes itself (ProjectService.ts:416-423) without that lock. Leaving this open for the author to decide.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Failing check Label PR 2 (vouch labeler) is not this PR's: it fails in hustcer/setup-nu ("No release for Nushell matching version specifier *") before any repo code runs. No fix exists yet; it needs a pinned Nushell version in that workflow. Re-running once.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown

Thread transfer impact

⚠️ The latest CI run did not produce a thread transfer result for 34eb902.

This comment will update automatically after the next completed run.

Copy link
Copy Markdown
Owner Author

CI run 39811700167 never ran on 34eb902: every job (Lint, Test Server 1-8, Build and Typecheck, …) was cancelled after 24h queued. They target blacksmith-* runners, which this fork has none of. Not this PR's failure. A fix is runner config (install Blacksmith or swap to ubuntu-latest), outside this PR. Local checks for this diff passed (check:changed, focused tests).


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 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.

2 participants