Skip to content

fix-forward #2829 (tsk-3vldwp): per-user broadcast read/archived state is RED on its own head (5 failures: no such column: n.user_id + mark_read/archive no longer reflected in lists) and ships zero tests - #2834

Merged
jaylfc merged 2 commits into
devfrom
exec/tsk-kuslln
Sep 6, 2026

Conversation

@jaylfc

@jaylfc jaylfc commented Sep 6, 2026

Copy link
Copy Markdown
Owner

CARD TITLE (intent, not commit subject): fix-forward #2829 (tsk-3vldwp): per-user broadcast read/archived state is RED on its own head (5 failures: no such column: n.user_id + mark_read/archive no longer reflected in lists) and ships zero tests

Autonomous build of board card tsk-kuslln.

REVISION: built on exec/tsk-3vldwp (cut at eb4caebd208c7a76359625019767a63db380a804), not on dev. That branch's
commits are ancestors of this one. Verified by git merge-base --is-ancestor
before the PR was opened.

  • Fix unread_count query missing table alias for notifications
  • Fix list() query to exclude broadcasts archived by the user
  • Update API tests to verify per-user state with user-scoped queries
  • Add RED-FIRST tests for per-user broadcast mark_read/archive
  • Add changelog fragments

Fixes: 5 CI failures (no such column: n.user_id + mark_read/archive not reflected in lists)

Files:
.../tsk-3vldwp-notification-broadcast-state.md | 4 +
.../tsk-kuslln-notification-broadcast-state.md | 3 +
tests/test_notifications.py | 10 +-
tests/test_notifications_user_scope.py | 61 ++++++
tinyagentos/notifications.py | 204 ++++++++++++++++-----
5 files changed, 233 insertions(+), 49 deletions(-)

- Fix unread_count query missing table alias for notifications
- Fix list() query to exclude broadcasts archived by the user
- Update API tests to verify per-user state with user-scoped queries
- Add RED-FIRST tests for per-user broadcast mark_read/archive
- Add changelog fragments

Fixes: 5 CI failures (no such column: n.user_id + mark_read/archive not reflected in lists)
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 44 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: e511f6be-b239-4efe-bf8b-d40d8247f093

📥 Commits

Reviewing files that changed from the base of the PR and between 04ba39b and 376722d.

📒 Files selected for processing (5)
  • changelog.d/tsk-3vldwp-notification-broadcast-state.md
  • changelog.d/tsk-kuslln-notification-broadcast-state.md
  • tests/test_notifications.py
  • tests/test_notifications_user_scope.py
  • tinyagentos/notifications.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Sep 6, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

async def mark_all_read(self, user_id: str | None = None) -> int:
if user_id is not None:
# Mark all per-user notifications (both user-specific and broadcasts for that user) as read
cursor = await self._db.execute(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: mark_all_read(user_id=...) corrupts broadcast read state for all users

The UPDATE at this line sets read = 1 on ALL broadcasts (user_id IS NULL), not just the current user's. When other users subsequently query their unread count or active list, COALESCE(nus.read_at, n.read) will return the globally-mutated n.read = 1, making broadcasts appear as read to every user regardless of whether they actually read them.

This is a cross-user state leak — one user's "mark all read" permanently affects all other users' view of broadcasts.

Suggested fix: only update per-user state for broadcasts instead of the shared column:

UPDATE notification_user_state SET read_at = ? WHERE user_id = ?

For broadcasts that have no per-user state row yet, insert one rather than touching the global read column.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.


if row and row[0] is None:
# This is a broadcast notification - upsert into per-user state
await self._db.execute(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: INSERT OR REPLACE in broadcast mark_read destroys existing archived_at state

When a broadcast that was previously archived by a user is later marked as read, INSERT OR REPLACE deletes the existing notification_user_state row and inserts a new one with only read_at set. The user's prior archived_at timestamp is permanently lost.

After this, list_archived() will no longer show the broadcast for that user, and list() may incorrectly include it in the active feed.

Use an UPDATE-then-INSERT pattern instead of INSERT OR REPLACE to preserve both columns independently.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

if row and row[0] is None:
# This is a broadcast notification
ts = int(time.time())
await self._db.execute(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: INSERT OR REPLACE in broadcast archive destroys existing read_at state

When a broadcast that was previously marked as read by a user is later archived, INSERT OR REPLACE deletes the existing notification_user_state row and inserts a new one with only archived_at set. The user's prior read_at timestamp is permanently lost.

After this, the archived notification will show as unread in list_archived() because COALESCE(NULL, n.read) falls back to the global (possibly 0) value.

Use an UPDATE-then-INSERT pattern instead of INSERT OR REPLACE to preserve both columns independently.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

(int(time.time()), user_id),
)
await self._db.commit()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: mark_all_read() return value is inaccurate

Returns cursor.rowcount from the shared column UPDATE only, ignoring rows updated in notification_user_state. If all notifications were already read but per-user state rows were updated, the return is 0 despite actual work being done.

Consider returning the sum of both UPDATE counts for an accurate result.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Sep 6, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 3
WARNING 1
Issue Details (click to expand)

CRITICAL

File Line Issue
tinyagentos/notifications.py 435 mark_all_read(user_id=...) sets global read = 1 on broadcasts, corrupting read state for all other users
tinyagentos/notifications.py 339 mark_read() for broadcasts uses INSERT OR REPLACE, destroying existing archived_at state
tinyagentos/notifications.py 372 archive() for broadcasts uses INSERT OR REPLACE, destroying existing read_at state

WARNING

File Line Issue
tinyagentos/notifications.py 447 mark_all_read() return value only reflects shared column updates, ignoring per-user state updates
Files Reviewed (5 files)
  • tinyagentos/notifications.py - 4 issues
  • tests/test_notifications.py
  • tests/test_notifications_user_scope.py
  • changelog.d/tsk-3vldwp-notification-broadcast-state.md
  • changelog.d/tsk-kuslln-notification-broadcast-state.md

Fix these issues in Kilo Cloud


Reviewed by step-3.7-flash:free · Input: 147.1K · Output: 30.7K · Cached: 139.6K

jaylfc added a commit that referenced this pull request Sep 6, 2026
fix-forward #2834 (tsk-kuslln): add the fenced red run (tests/test_notifications_user_scope.py broadcast per-user state) to the PR body; no code change
@jaylfc
jaylfc merged commit 376722d into dev Sep 6, 2026
29 of 33 checks passed
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.

1 participant