Title: [High][store] notifySubscribers iterates live array → subscribers skipped on unsubscribe-during-notify · Issue #165 · bQuery/bQuery · GitHub
Open Graph Title: [High][store] notifySubscribers iterates live array → subscribers skipped on unsubscribe-during-notify · Issue #165 · bQuery/bQuery
X Title: [High][store] notifySubscribers iterates live array → subscribers skipped on unsubscribe-during-notify · Issue #165 · bQuery/bQuery
Description: Severity: 🟠 High (correctness) Location src/store/create-store.ts:157-159 (notifySubscribers); unsubscribe splices at :352-360. Description $subscribe stores callbacks in a subscribers array and returns an unsubscribe that splices the ca...
Open Graph Description: Severity: 🟠 High (correctness) Location src/store/create-store.ts:157-159 (notifySubscribers); unsubscribe splices at :352-360. Description $subscribe stores callbacks in a subscribers array and re...
X Description: Severity: 🟠 High (correctness) Location src/store/create-store.ts:157-159 (notifySubscribers); unsubscribe splices at :352-360. Description $subscribe stores callbacks in a subscribers array and re...
Opengraph URL: https://github.com/bQuery/bQuery/issues/165
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"[High][store] notifySubscribers iterates live array → subscribers skipped on unsubscribe-during-notify","articleBody":"## Severity: 🟠 High (correctness)\n\n## Location\n[`src/store/create-store.ts:157-159`](../blob/main/src/store/create-store.ts#L157-L159) (`notifySubscribers`); unsubscribe splices at `:352-360`.\n\n## Description\n`$subscribe` stores callbacks in a `subscribers` array and returns an unsubscribe that `splice`s the callback out. `notifySubscribers` iterates that **live** array:\n```ts\nfor (const callback of subscribers) {\n callback(currentState);\n}\n```\nIf any callback unsubscribes itself (or another subscriber) during notification, the `splice` shifts indices under the `for…of` iterator, so the next subscriber is **silently skipped** for that notification cycle (and a newly-subscribed one may be double-invoked). A watcher that self-detaches on a condition is a common pattern, so this is readily triggered.\n\nNote that [`$onAction` already guards against exactly this](../blob/main/src/store/create-store.ts#L274) with `const listenerSnapshot = [...actionListeners];` — `notifySubscribers` is simply missing the same treatment.\n\n## Reproduction\n```ts\nconst store = createStore({ id: 's', state: () =\u003e ({ n: 0 }) });\nconst unsubA = store.$subscribe(() =\u003e unsubA()); // self-detaches on first notify\nconst seen = [];\nstore.$subscribe(() =\u003e seen.push('B')); // registered after A\nstore.n = 1; // A runs, splices itself, B is skipped\n// seen === [] (B should have fired)\n```\n\n## Suggested fix\nSnapshot before iterating, matching the `$onAction` pattern:\n```ts\nfor (const callback of [...subscribers]) {\n callback(currentState);\n}\n```\n\n---\n_Filed as part of a full-codebase security \u0026 correctness audit._\n","author":{"url":"https://github.com/JosunLP","@type":"Person","name":"JosunLP"},"datePublished":"2026-07-05T17:02:47.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/165/bQuery/issues/165"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:29219a02-b6e8-b32d-0470-45047fde3ee2 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | A076:30372D:1835120:234B429:6A4E552F |
| html-safe-nonce | fc3eec25b80e91ea13670b4b6fd8539e818cee08ee14dc5d2a53c38387d4993d |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJBMDc2OjMwMzcyRDoxODM1MTIwOjIzNEI0Mjk6NkE0RTU1MkYiLCJ2aXNpdG9yX2lkIjoiNzE1Nzg0NjM5OTY4OTU3Nzc3NSIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | c77c9559750525c9edd67051376e3084942b570751ababea3d196caba22d2d2a |
| hovercard-subject-tag | issue:4813692851 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/bQuery/bQuery/165/issue_layout |
| twitter:image | https://opengraph.githubassets.com/ae351c58c617792cda07f37b1249821532edd3dbe27f6646c7b469ed60109bec/bQuery/bQuery/issues/165 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/ae351c58c617792cda07f37b1249821532edd3dbe27f6646c7b469ed60109bec/bQuery/bQuery/issues/165 |
| og:image:alt | Severity: 🟠 High (correctness) Location src/store/create-store.ts:157-159 (notifySubscribers); unsubscribe splices at :352-360. Description $subscribe stores callbacks in a subscribers array and re... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | JosunLP |
| hostname | github.com |
| expected-hostname | github.com |
| None | 030096ee0db095447bfe77409d33bfac127ca7128299c58deef27c52eaa1b1f0 |
| turbo-cache-control | no-preview |
| go-import | github.com/bQuery/bQuery git https://github.com/bQuery/bQuery.git |
| octolytics-dimension-user_id | 256381806 |
| octolytics-dimension-user_login | bQuery |
| octolytics-dimension-repository_id | 1139284244 |
| octolytics-dimension-repository_nwo | bQuery/bQuery |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 1139284244 |
| octolytics-dimension-repository_network_root_nwo | bQuery/bQuery |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | f344f1eab54fc7dfc02d0d018cda7e158c3db65c |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width