Skip to content

fix(multiuser): fix cross-user queue badge, progress bar, and gallery hijacking#9314

Open
lstein wants to merge 5 commits into
mainfrom
fix-multiuser-badge-count
Open

fix(multiuser): fix cross-user queue badge, progress bar, and gallery hijacking#9314
lstein wants to merge 5 commits into
mainfrom
fix-multiuser-badge-count

Conversation

@lstein

@lstein lstein commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

This PR fixes three related multiuser-mode issues that all stem from how socket events are (correctly) isolated per user, while frontend state was assuming it sees every relevant event.

1. Queue badge shows the wrong global total

Reproduction: User A queues 2 jobs; User B immediately queues 2 jobs. User A's badge shows 2/2 (wrong — should be 2/4); User B's shows 2/4 (correct).

Root cause: queue_item_status_changed and batch_enqueued events are emitted only to the owning user's socket room (privacy — they carry unsanitized user_id/batch_id/session_id). The frontend badge only refetches the global queue status in response to those events, so a non-admin only learns about queue changes from their own jobs. Whoever queued first captured the global total before the other user's jobs existed, then stopped refetching.

2. Progress bar animates indefinitely

Same root cause: once a user's own jobs finish they get no further signal, so the global in_progress count freezes at a non-zero value. ProgressBar.tsx reads that frozen in_progress > 0 (with lastProgressEvent null) and stays isIndeterminate forever.

Fix for 1 & 2

Broadcast a content-free queue_counts_changed event to the whole queue room whenever counts change (a status transition or an enqueue). It carries only queue_id — no user_id, batch_id, session_id, or counts — so every subscriber simply refetches GET /queue/{id}/status, which already redacts per-user data. Nothing private is leaked. It never fires on high-frequency invocation-progress events.

  • invokeai/app/api/sockets.py_broadcast_queue_counts_changed() helper, called from the status-changed and batch-enqueued branches of _handle_queue_event.
  • invokeai/frontend/web/src/services/events/types.ts — type the new event.
  • invokeai/frontend/web/src/services/events/setEventListeners.tsx — handler invalidating only SessionQueueStatus.

3. Another user's image hijacks an admin's gallery

Reproduction: Admin (User A) and unprivileged User B logged in on separate browsers. Any image B renders switches A's selected board to B's board and displays B's image. Does not happen between two unprivileged users.

Root cause: Admins are in the admin socket room and receive invocation_complete events for every user. addImagesToGallery ran for all of them, inserting other users' images into the admin's gallery and auto-switching the admin's selected board.

Fix for 3

Gate addImagesToGallery on ownership: when an authenticated user is present and the event's user_id differs from theirs, skip the gallery insertion and auto-switch. In single-user mode there is no authenticated user, so the original behavior is preserved.

  • invokeai/frontend/web/src/services/events/onInvocationComplete.tsx

Testing

  • Manually verified with two users (2 + 2 jobs): both badges settle on 2/4, count down together, and clear with no lingering spinner.
  • Manually verified admin no longer has their board switched when another user generates an image.
  • tsc --noEmit, eslint --max-warnings=0, prettier --check all pass.
  • ruff check and ruff format --check pass.

🤖 Generated with Claude Code

In multiuser mode, queue item status changes and batch-enqueued events are
emitted only to the owning user's socket room (for privacy). The frontend badge
and progress bar only refetch the global queue status in response to those
events, so a non-admin only learns about queue changes from their *own* jobs.
Once their jobs finish they get no further signal: the global total (the "/Y"
in "X/Y") and the in_progress count freeze, leaving the badge wrong and the
progress bar animating indefinitely.

Broadcast a content-free `queue_counts_changed` event to the whole queue room
whenever counts change (a status transition or an enqueue). It carries only the
queue_id — no user_id, batch_id, session_id, or counts — so every subscriber can
refetch GET /queue/{id}/status, which already redacts per-user data. Nothing
private is leaked, and it never fires on high-frequency invocation progress
events.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added api python PRs that change python files frontend PRs that change frontend files labels Jun 29, 2026
@lstein lstein added the 6.14.x label Jun 29, 2026
@lstein lstein moved this to 6.14.x Theme: USER EXPERIENCE in Invoke - Community Roadmap Jun 29, 2026
In multiuser mode, admins are subscribed to the "admin" socket room and so
receive invocation_complete events for every user. addImagesToGallery ran for
all of them, so any image generated by another user would insert into the
admin's gallery and auto-switch the admin's selected board to that user's board,
displaying their image. This did not happen between two unprivileged users
because they never receive each other's events.

Gate addImagesToGallery on ownership: when an authenticated user is present and
the event's user_id differs from theirs, skip the gallery insertion and
auto-switch entirely. In single-user mode there is no authenticated user, so the
original behavior is preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lstein lstein changed the title fix(multiuser): keep queue badge & progress bar live across users fix(multiuser): fix cross-user queue badge, progress bar, and gallery hijacking Jun 29, 2026
The badge-livening change emits a redacted queue_counts_changed nudge to
the whole queue room on status changes and batch enqueues. Update the two
routing tests to check per-event: the private event still must not reach
the queue_id room, but the data-free counts nudge legitimately does.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the python-tests PRs that change python tests label Jun 30, 2026

@JPPhoto JPPhoto left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I found one thing so far.

  • invokeai/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx:262

    The new cross-user guard only wraps addImagesToGallery() at lines 48-61, but admin clients still process another user's invocation_complete before that guard. The backend sends every invocation event to the admin room in invokeai/invokeai/app/api/sockets.py:304-316, and InvocationCompleteEvent carries the owner user_id. When an admin receives another user's completion event, the handler still reads and upserts $nodeExecutionStates at lines 265-280, clears canvas workflow integration processing at lines 283-284, and clears $lastProgressEvent at line 289. This can mark the admin's local workflow nodes complete, append another user's outputs to matching node IDs, or stop the admin's canvas workflow integration spinner even though the event was not theirs. Common node IDs make this realistic, and getUpdatedNodeExecutionStateOnInvocationComplete() creates state even when one did not exist.

    To expose this issue, add a frontend vitest for buildOnInvocationComplete that sets auth.user.user_id to an admin user, seeds $nodeExecutionStates or canvas integration processing state, invokes the handler with data.user_id for a different user, and asserts that node execution state, canvas processing state, gallery state, and $lastProgressEvent are not mutated.

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

Labels

6.14.x api frontend PRs that change frontend files python PRs that change python files python-tests PRs that change python tests

Projects

Status: 6.14.x Theme: USER EXPERIENCE

Development

Successfully merging this pull request may close these issues.

2 participants