Skip to content

Make a repeat channel delete a no-op instead of a second announcement and audit row - #691

Merged
davidmckayv merged 8 commits into
CopilotKit:mainfrom
Chebaleomkar:fix/channel-delete-once
Oct 2, 2026
Merged

davidmckayv merged 8 commits into
CopilotKit:mainfrom
Chebaleomkar:fix/channel-delete-once

Conversation

@Chebaleomkar

Copy link
Copy Markdown
Contributor

What this changes

softDelete's comment says its deleted_at guard "is what makes a repeat call a no-op rather than a new stamp", and the existing test is "deleting again is a no-op, not an error". The guard kept the timestamp, but the rest of a delete still ran on every repeat:

  • the member list was read and pg_notify told every member the channel was deleted, again;
  • the route then called recordDeleted, which wrote another channel.deleted row to audit_events. That table is append-only, so a retry, a second tab or a double click left rows for deletions that did not happen. The comment above recordDeleted says "The trail records acts, not attempts."

softDelete now returns whether this call deleted the channel. The update uses .returning(); on zero rows it returns false before reading members or notifying. The route records only when the store did not answer false, and still answers 204 either way, so DELETE stays idempotent for the client.

ChannelStore.softDelete changes from Promise<void> to Promise<boolean>. Test fakes that resolve undefined are treated as "deleted", so existing route tests keep their behaviour. The test files are outside server/tsconfig.json's include.

Where it runs

  • New state that outlives a request? None.
  • What happens on the second replica? Two replicas deleting at once: the guarded UPDATE ... RETURNING stamps one row once, so exactly one of them announces and records.
  • Anything serialised? The existing guarded update is the serialisation; no check-then-write is added.
  • Anything fanned out to a browser? The same pg_notify, now only for the call that deleted.
  • New listener, port, or schedule? No.

Boundary and audit

  • A real deletion still writes its channel.deleted row. A repeat no longer writes one, because nothing happened.
  • Nothing new is trusted from the client.

Changelog

  • A line under Unreleased.

Proof

Test upstream routes.ts this branch
channel-events.integration, new: a repeat delete announces nothing to either member (real LISTEN/NOTIFY, with a 500 ms window) fail pass
channel-routes, new: the route answers 204 and writes no audit row when the store answers false fail pass
channel-routes, existing "deleting again is a no-op", now asserting true then false fail pass

channel-routes, channel-events.integration, runtime-agents.integration and routines-store.integration pass 165 of 165 against pgvector/pgvector:pg17. tsc --noEmit exits 0, and biome is clean.

… and audit row

softDelete's deletedAt guard kept the timestamp, but the member announcement and the route's channel.deleted audit row still ran on every repeat, so a retry or a second tab told every member again and wrote a row to the append-only trail for a deletion that did not happen. softDelete now reports whether this call deleted the channel, announces only then, and the route records only then. The response is still 204.
davidmckayv
davidmckayv previously approved these changes Oct 2, 2026
@davidmckayv
davidmckayv enabled auto-merge (squash) October 2, 2026 03:56
auto-merge was automatically disabled October 2, 2026 11:12

Head branch was pushed to by a user without write access

davidmckayv
davidmckayv previously approved these changes Oct 2, 2026

@davidmckayv davidmckayv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The current head 99277ff matches the accepted source disposition from the refreshed triage. Required CI must pass before landing.

@davidmckayv
davidmckayv enabled auto-merge (squash) October 2, 2026 16:52
auto-merge was automatically disabled October 2, 2026 18:59

Head branch was pushed to by a user without write access

Chebaleomkar and others added 4 commits October 3, 2026 00:47
softDelete now says whether it deleted anything, which the reset hook's
Promise<void> type refused, so the server no longer typechecked. The
reset ignores the answer, so the hook takes any result.
@davidmckayv

Copy link
Copy Markdown
Contributor

I pushed one commit to fix the server typecheck. softDelete now returns whether it deleted anything, but the Bot reset's softDeleteChannel hook (server/src/agents/lifecycle-reset.ts) was typed Promise<void>, so server/src/index.ts no longer compiled. The reset ignores the return value, so the hook now accepts any result.

@davidmckayv
davidmckayv merged commit bf1fd11 into CopilotKit:main Oct 2, 2026
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