Conversation
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: needs maintainer review before merge. Reviewed September 13, 2026, 9:32 AM ET / 13:32 UTC (Revision 3). ClawSweeper reviewWhat this changesAdds permanent owner-controlled channel deletion through the web app, API, SDK, and CLI, with confirmation, upload cleanup, viewer notifications, and database deletion optimizations. Merge readiness✅ Ready for maintainer review The feature remains absent from main and v0.5.0. The prior CLI and PostgreSQL findings are addressed, and the supplied behavior evidence supports this revision; no blocking correctness finding remains. Priority: P2 Review scores
Verification
How this fits togetherClickClack’s channel administration connects authenticated workspace users to SQL-backed conversations and uploaded files. Deletion removes channel-owned records transactionally, queues file cleanup, and informs connected clients through realtime events. flowchart TD
A[Web app or CLI] --> B[Authenticated deletion API]
B --> C[Owner and channel safeguards]
C --> D[Transactional database deletion]
D --> E[Durable file cleanup]
D --> F[Realtime deletion event]
F --> G[Viewers leave deleted channel]
Before mergeNone. Agent review detailsSecurityNone. Review metrics
Technical reviewBest possible solution: Keep permanent deletion explicitly targeted and owner-authorized, preserve shared files, and retain archive as the reversible alternative. Do we have a high-confidence way to reproduce the issue? Not applicable as a feature request: main lacks permanent channel deletion. The previous CLI defect is addressed by source, regression coverage, and supplied before/after terminal output. Is this the best way to solve the issue? Yes. Reusing transactional cascades, existing authorization, and durable upload cleanup provides a coherent implementation without replacing archive or changing saved channel defaults. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning medium; reviewed against eeefa0412808. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (2 earlier review cycles)
|
b6d6079 to
aa459dd
Compare
Owners delete a channel from Channel settings, the API, or the CLI after reviewing what will be removed and typing the channel name. The channel's messages, replies, reactions, pins, topics, read state, and exclusive uploads go in one transaction; a workspace-scoped channel.deleted event moves viewers out. The last channel and the Guests workspace's provisioned channels cannot be deleted. The CLI deletes only a channel named with --channel on its command line, never CLICKCLACK_CHANNEL or the saved default. On SQLite, cascading many message deletions scanned both the messages table and the FTS index once per row. Child-key indexes and a message-to-search-row map make a 10k-message channel in a 120k-message workspace delete in 0.3 s instead of 6.6 minutes, which also speeds up workspace deletion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aa459dd to
c6d1ef8
Compare
Maintainer edits
MUST: Keep Allow edits from maintainers enabled for this PR so maintainers
can help update the branch when needed.
What Problem This Solves
Workspace owners can archive a channel but cannot remove a channel they no longer
want, together with its messages and files.
User Impact
User impact: owners can delete a channel from Channel settings → Delete
channel..., from the API, or with
clickclack channels delete. The dialogshows what will be removed (messages, thread replies, pins, topics, files and
their size), offers to archive instead, and enables deletion only after the
channel name is typed. Deletion is permanent. People viewing the channel are
moved to their fallback conversation with a notice. The server refuses to
delete a workspace's last channel and the Guests workspace's provisioned
channels. On SQLite, two new migrations make deleting many messages fast: a
10k-message channel in a 120k-message workspace now deletes in 0.3 s instead of
6.6 minutes. Existing workspace deletion benefits the same way.
Why This Change Was Made
channel, messages, replies, reactions, pins, channel topics, read pointers,
and notification settings go together. Uploads attached only to that channel
are removed and their objects go through the durable cleanup queue already
used for workspace deletion; uploads still used elsewhere and the workspace
icon are kept. The audit log records the channel and the removed counts.
messagesto find replies and quotes (parent_message_idandquoted_message_idhad no index), and one of the FTS5 search index, becausethe delete trigger matched the unindexed
messages_fts.message_id. Deletinga channel therefore grew with channel size × workspace size. New partial
indexes cover the child keys on both stores, and
message_search_rowsrecordseach message's FTS rowid so the search triggers update rows by rowid. Both
are needed; either alone leaves most of the cost.
channel.deletedevent tells clients to leave the channel.Earlier events for the channel stay in the log, so replay cursors remain valid;
realtime delivery already skips events whose channel no longer exists.
FOR NO KEY UPDATE) beforeit counts or selects anything, in the same workspace-first order as workspace
deletion and updates. Competing deletions cannot both pass the last-channel
check, and the workspace icon cannot move onto an upload being removed; event
appends and inserts, which take
KEY SHARE, are not blocked. Candidate uploadsare then locked
FOR UPDATEand re-checked, so an attachment elsewhere eithercommits first (and the upload is kept) or waits and fails its foreign key.
403;blocked deletions return
409with ablockercode the web app explains.clickclack channels deletedeletes only a channel named with--channelonits own command line (global or subcommand form).
CLICKCLACK_CHANNELand thesaved default channel pick the channel for everyday commands, so they never
choose what gets deleted. Without
--yesthe command prints the counts andexits.
The new migrations (
sqlite/0043,sqlite/0044,postgres/0036) share numberswith the agent question migrations in #258. Migrations apply by file name, so both
work together; whichever PR lands second can renumber to keep the sequence tidy.
Evidence
The CLI with a default channel in the environment, real server and CLI from
the same build:
Previous head `aa459dd5`:
channels delete --yesdeleted#launchfromCLICKCLACK_CHANNELThis head: the default channel is ignored; the named channel is deleted after the preview
Tests:
channeldeletiontest): thechannel's messages, topics, search results, and exclusive upload are removed
while other channels, shared uploads, and the workspace icon survive; the
deletion counts match the preview; a replay cursor pointing at one of the
channel's old events still replays forward to
channel.deleted; guard railsfor the last channel, provisioned Guests channels, archived channels, and
non-owners.
TestCascadeChildKeysUseIndexes(SQLite query plan) andTestCascadeChildKeyIndexesExist(Postgres).TestDeleteChannelSerializesLastChannelDecisionsruns two deletions of aworkspace's last two channels (one succeeds, one gets
last_channel), andTestDeleteChannelKeepsUploadsAttachedDuringDeletionattaches the channel'sonly upload elsewhere while the deletion is paused after choosing it (the
attachment either keeps the upload or fails). Both pass three times in a row;
against the previous revision they fail with
deleted=2 blocked=0 remaining=0and
attachment succeeded but kept 0 rows.TestSearchTriggersFindRowsThroughTheRecordedRowid: upgrades a database withan existing message, detaches the search rows from their message IDs, then
edits one message and deletes the other's channel; both rows are still
replaced or removed and search returns the edited message. With the old
message_idtriggers it fails withdetached rows = 2.TestChannelDeletionHTTP:403for members and bot tokens,404forunknown or already deleted channels,
204for the owner, the live andreplayed
channel.deletedevent on a member's socket, the audit entry, andthe
last_channelblocker with409.TestChannelsDeleteRequiresExplicitConfirmation: the CLI prints the previewand refuses without
--yes.TestChannelsDeleteIgnoresDefaultChannelsruns the CLI entry point withCLICKCLACK_CHANNEL, then with a saved default channel:channels delete --yessends no DELETE, while
--channelin either flag position deletes. Without thefix it fails with
CLICKCLACK_CHANNEL chose the channel to delete.tests/e2e/channel-deletion.spec.ts: an owner deletes after reviewing thepreview and typing the name; a member watching the channel is moved out with
a notice; the last channel cannot be deleted.
apps/web/src/lib/channel-deletion.test.ts: name confirmation, blockermessages, and the notice text.
pnpm fmt:check,pnpm lint,pnpm typecheck,pnpm -r typecheck, web unit tests,pnpm docs:site, andgo test ./...with
CLICKCLACK_POSTGRES_TEST_DSN, deadcode, and the embedded build iscurrent and repeatable (coverage gate 86.8%). On this machine these tests also
fail on unchanged
main:TestHTTPBodyDeadlineStillBoundsStalledRequestBodies,uploadstore TestR2HeaderNetworkLifecycle/progressing_PUT, and intermittentlyTestHTTPErrorPathsAndSPA(1 of 4 runs onmain), andTestHTTPSlashCommandRequiresChannelWriteAuthorityBeforeCallback(1 of 5 runson
main, 5 of 5 pass on this branch). The first CI run's Playwright jobfailed once in
chat.spec.ts › clicking the active conversation does not refetch its messages; locally that test passes 3 of 3 alone and the wholechat.spec.tspluschannel-deletion.spec.tspass 42 of 42 with two workers.Deleting a channel on SQLite (Python
sqlite33.45 applying the realmigrations; 120,000 messages in 12 channels; the deleted channel has 7,000
roots, 2,500 thread replies, and 500 quote replies):
mainApplying this PR's migrations to that populated database took 0.14 s. A
smaller run (30,000 messages, 3,000 deleted) shows the two fixes are
independent: 27.7 s on
main, 11.5 s with only the search row map, 17.7 s withonly the indexes, and 0.10 s with both.
AI-assisted: prepared with Claude Code; I reviewed the change and the evidence.
🤖 Generated with Claude Code