Skip to content

Stop task actions and invitation emails deadlocking with a member leaving - #29

Merged
EmotiveImpact merged 5 commits into
mainfrom
claude/app-review-task-locks
Oct 12, 2026
Merged

EmotiveImpact merged 5 commits into
mainfrom
claude/app-review-task-locks

Conversation

@EmotiveImpact

Copy link
Copy Markdown
Owner

Before: if a member left while someone completed, skipped, reopened, handed over or undid one of their tasks, the two could wait on each other until PostgreSQL cancelled one as a deadlock, so either the task action or the departure failed. Emailing an invitation could do the same with an admin who was leaving, being removed or being made a member.

After: each waits for the other to finish. A task action then goes ahead on the task as the departure left it or is refused with its usual message, and an invitation email whose invitation was withdrawn meanwhile is refused with the existing "Invitation is unavailable" message, with nothing sent.

This follows up batch 1, where editing and removing a task were changed to lock the commune first (0027). It is built on #28 (batch 4), so review that first; the diff here shows only this change.

What changed

Area Change
Tasks Migration 0037 patches completing, skipping, reopening, handing over and undoing in place, as 0027 did for edits and removals: the task's commune is locked first, then the task (for undo, the task through its occurrence) is locked and read as before. Every check and message is unchanged, and each patch asserts that it applied and that the commune is the first row its function locks.
Invitation email The reminder worker's sender takes a key-share lock on the invitation's commune before it reads the invitation, as accepting an invitation and inviting the same address again already do.

Releasing

Nothing hosted was touched. Migration 0037 is local only, follows 0036 and needs no client change. The sender change ships with the next reminder worker deploy, in either order with 0037.

How it was checked

  • task_lock_order.mjs holds a member's departure between its commune and member locks while each of the five task commands runs, as it already did for edits and removals. Each race must finish without a deadlock, the departure must succeed, and the command either goes ahead or is refused with its usual message. Each session ends its transaction as soon as its statement settles, so a deadlock fails the test in about a second.
  • The worker's invitation test holds an admin's removal the same way while the real sender runs for that admin's invitation. The removal must succeed and the sender must be refused with the existing 404, leaving no delivery row and never calling the mocked email provider.
  • Every other suite on the final branch passes: 24 database tests through run-local.sh (2 skipped as they need a Neon branch), 18 local API suites, 458 web tests, the worker's tests, typechecks for the web app, local API and worker, and a production build.

How

Only the start of each function changes, so each is patched with pg_get_functiondef and replace(), and the migration stops if a definition no longer matches. A task never moves to another commune, so the commune read before locking is still the task's once the locks are held. Nothing else locks a task or an occurrence before its commune: adding a task locks the commune first, and the snapshot and search only read.

🤖 Generated with Claude Code

https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS


Generated by Claude Code

@vercel

vercel Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
commune-web Error Error Oct 9, 2026 4:50pm UTC

claude added 5 commits October 9, 2026 16:50
…oing

Completing, skipping, reopening, handing over and undoing a task locked the
task first and needed the commune later, when the foreign key on the
occurrence or activity line they add takes a key-share lock on it. A member
leaving locks the commune and then their tasks, so the two could wait on
each other until PostgreSQL cancelled one as a deadlock.

0037 patches the three functions in place, as 0027 did for edits and
removals: the task's commune is read without a lock and locked, then the
task (for undo, the task through its occurrence) is locked and read as
before. Every existing check and message is unchanged, and each patch
asserts that it applied and that the commune is the first row locked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
The lock order test now holds a departure between its commune and member
locks while Casey completes, skips, hands over or undoes, as it already did
for an edit and a removal. Each race must finish without a deadlock, the
departure must succeed, and the command either goes ahead on the task as
the departure left it or is refused with its usual message. Each session
now ends its transaction as soon as its statement settles, so a deadlock
fails the test in about a second rather than after a statement timeout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
The invitation email sender locked the invitation first and needed the
commune later, when the foreign key on the delivery row it adds takes a
key-share lock on it. An admin leaving, removed or made a member locks the
commune and then withdraws their invitations, so the two could wait on
each other until PostgreSQL cancelled the sender as a deadlock.

The sender now takes a key-share lock on the invitation's commune, found
by the token hash, in its own statement first. It then waits for such a
change to finish and refuses with the existing "Invitation is unavailable"
404. Accepting an invitation and inviting the same address again already
lock the commune before the invitation, so the order now matches them too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
The invitations test now holds an admin's removal between its commune and
member locks while the real sendInvitationEmail runs for that admin's
invitation, so the interleaving happens every time. Neither side may fail
as a deadlock, the removal must succeed, and the sender must be refused
with the existing 404, leaving no delivery row and never calling the
mocked provider.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
…notes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS

Copy link
Copy Markdown
Owner Author

The Vercel preview for commune-web shows Error here, as it has on every branch since 6 October. It is the project or account issue explained on #24 (app review finding 45), not this diff: the Web app check, which builds the app, passes on this head (79cdd9b). It needs someone with the Vercel login to read a failed deployment's log.


Generated by Claude Code

@EmotiveImpact
EmotiveImpact changed the base branch from claude/app-review-batch-4 to main October 12, 2026 02:00
@EmotiveImpact
EmotiveImpact marked this pull request as ready for review October 12, 2026 02:00
@EmotiveImpact
EmotiveImpact merged commit 70df300 into main Oct 12, 2026
6 of 7 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 12, 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-12T02:02:07.327784Z 79cdd9b Draft marked ready
ℹ️ 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.

This branch had an error being deployed

1 failed deployment
Preview — 79cdd9b8 Deployed Oct 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants