Skip to content

Sync Reverb and Scout updates - #49

Merged
binaryfire merged 13 commits into
0.4from
upstream-sync-framework-14
Oct 2, 2026
Merged

binaryfire merged 13 commits into
0.4from
upstream-sync-framework-14

Conversation

@binaryfire

@binaryfire binaryfire commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

This completes the Reverb update to laravel/reverb main at 74c8c4082c. It also brings Scout up to laravel/scout 11.x at ce2542f5a7, except for semantic and hybrid search (laravel/scout pull requests 1007, 1008, 1009 and 1012), which follow in their own PR. Hypervel already had most of Scout's other changes, so the Scout work is mainly upstream's missing tests, plus a fix for null search queries.

Upstream Updates

Reverb

  • laravel/reverb pull request 406 closes the connection when a WebSocket handshake fails. Swoole already does this after any non-101 response to an upgrade request, and an invalid key already got a 400. But the WebSocket server never checked Sec-WebSocket-Version, so a client asking for an unsupported version was upgraded anyway. It now gets RFC 6455's 426 response with Sec-WebSocket-Version: 13, as Ratchet's negotiator sends for upstream. Upstream's two handshake tests are ported, and the handshake tests now send the version header that real clients send.
  • laravel/reverb pull request 407 moved upstream's tests from Mockery to Double. Hypervel keeps Mockery. One client-event test expected a method the channel manager doesn't have never to be called, so it could never fail. It now checks that all() is never called, as upstream's does.
  • laravel/reverb commit 2f8a121813 rejects signed HTTP API requests whose auth_timestamp is missing or more than 600 seconds from the current time. Hypervel accepted any timestamp, so a captured request could be replayed indefinitely. Upstream's test for expired and future timestamps is ported, along with a check that a correctly signed request without a timestamp is rejected.
  • laravel/reverb pull request 408 fixed presence channels when Reverb scales across servers. Hypervel's design already covers most of it: member events travel as internal events that other servers don't cache, SharedState decides each user's first and last connection atomically across workers and servers, and presence members are gathered from every worker and server and merged. Two fixes are ported. A member without user_info made the subscription fail, and empty user_info was sent as []; both are now sent as {}. And if gathering members from other workers or servers failed after the subscription was committed, the client never got its subscription confirmation. The failure is now reported and the client gets this worker's members. Upstream's tests are ported, including its three Redis scaling tests.

Scout

  • laravel/scout pull request 955 defaults the Typesense import action to upsert. Hypervel already did, but only the default was tested. Upstream's emplace test is ported, and the test helper's config stub, which returned the default for every key, now takes config values.
  • laravel/scout pull requests 962, 996 and 1002 made the Scout jobs' retries and backoff configurable, added opt-in unique indexing jobs and marked the jobs as failed on timeout by default. Hypervel already had all three, and upstream's missing job tests are ported. The unique ID test expects Hypervel's sha256 key, where upstream's expects md5.
  • laravel/scout pull request 969 added where($field, $operator, $value), which Hypervel already supported. The collection and database engine tests for >, <, >=, <= and != are ported. The Meilisearch, Typesense and Algolia integration tests ran one combined query. They now run upstream's comparison cases with the changes from laravel/scout pull requests 976 and 978, which filter a typed numeric field and accept two results in either order. Typesense filters its integer ranking field, since its id is a string, and Algolia keeps its escaped-string case.
  • laravel/scout pull request 1005 fixed boolean and inequality Algolia filters. Hypervel already had the fix, and its test gains upstream's boolean whereIn and whereNotIn cases.
  • laravel/scout pull request 1011 fixed collection searches for "0", which Hypervel already handled. Upstream's new test also searches for null, which search() rejected: Model::search($request->query('q')) threw a TypeError when the request had no q. search() and the Builder constructor now accept null, and the builder stores it as an empty query. Upstream's null, whitespace and paginate-for-zero tests are ported.

Additional Hypervel Fixes

  • Scout's README listed internal changes, enhancements and fixes as differences from Laravel, and missed that pausing search syncing only applies to the current coroutine. It now lists only the differences that ported Laravel code has to account for, links the documentation and follows the standard package layout.
  • Three tests could fail depending on where in a second they ran, and two of them occasionally did in CI. The database and file cache stores report lock lifetimes in whole seconds, so the funnel lease refresh test could read the same lifetime before and after a refresh when a second ended between the reads. It now waits long enough for the refreshed lifetime to be longer. The duration limiter's windows end on a whole second, so the two tests that use a one-second window could open it at the very end of a second and let the immediate second attempt into a new window. They now start just after a whole second.

The changed tests, the Reverb unit and integration suites, the WebSocket server tests, the Scout unit and feature tests, the Meilisearch and Typesense filtering integration tests, formatting and static analysis pass locally. The Algolia integration tests weren't run locally because they need Algolia credentials, and CI runs them only when those credentials are configured.


Summary by cubic

Syncs laravel/reverb and laravel/scout to their latest upstream releases, with most changes landing as ported tests since Hypervel already had most of the implementations.

  • Rejects signed Reverb HTTP API requests whose auth_timestamp is missing or more than 600 seconds from the current time, preventing indefinite replay of captured requests.
  • Rejects WebSocket handshakes asking for an unsupported Sec-WebSocket-Version with RFC 6455's 426 response, after the key check. The live server test now asserts the Sec-WebSocket-Version: 13 header.
  • Presence subscriptions now send {} for members without user_info and answer with local members when presence gathering from other workers or servers fails.
  • search() and the Scout Builder now accept null queries, so Model::search($request->query('q')) no longer throws a TypeError when q is absent.
  • Scout's README now lists only the documented behavioral differences from Laravel, with a corrected tenant token entry.
  • Two cache and limiter tests now avoid whole-second timing races that occasionally failed Redis Cluster CI runs.

The changed tests, the Reverb unit and integration suites, the WebSocket server tests, the Scout unit and feature tests, the Meilisearch and Typesense filtering integration tests, formatting and static analysis pass locally. The Algolia integration tests weren't run locally because they need Algolia credentials, and CI runs them only when those credentials are configured.

Written for commit 0c706dc. Summary will update on new commits.

Review in cubic

Note

Add WebSocket version validation, Pusher signature timestamp checks, and presence fallback in Reverb/Scout sync

  • Server::onHandshake in Server.php now rejects missing or unsupported WebSocket versions before routing, returning HTTP 426 with upgrade headers.
  • The Pusher HTTP controller in Controller.php adds a 600-second SIGNATURE_TOLERANCE; verifySignatureTimestamp rejects signed requests with missing, nonnumeric, or out-of-window timestamps with a 401 error.
  • Presence channel responses in InteractsWithPresenceChannels.php now fall back to local channel members when metrics gathering fails, while rethrowing cancellation exceptions. The presence hash is keyed by user ID with empty objects for falsy user info.
  • Scout accepts a nullable search query: Builder::__construct, Searchable::search, and SearchableInterface::search normalize null to an empty string. Scout README is updated to reflect framework differences.
  • Adds extensive test coverage: comparison filters across all Scout engines, Reverb presence scaling, handshake rejection, and job unique-ID behavior.
  • Behavioral Change: previously accepted Pusher requests signed with old, future, or missing timestamps now return 401; WebSocket handshakes with unsupported versions now get 426 instead of proceeding; presence responses use user-ID keys with empty-object metadata.

Macroscope summarized 0c706dc.

laravel/reverb PR 406 closes the connection when a WebSocket handshake
fails. Swoole already does this: it closes the connection after any
non-101 response to an upgrade request, and an invalid key already gets
a 400. The handshake never checked Sec-WebSocket-Version, though, so a
client asking for an unsupported version got a 101 instead of RFC
6455's 426. The websocket-server handshake now rejects it after the key
check with a 426 carrying Upgrade, Connection and Sec-WebSocket-Version
13, as Ratchet's negotiator does for upstream.

Upstream's two handshake tests are ported to the Reverb integration
ServerTest with their five-second client timeout, and ServerHandshakeTest
covers the rejection before routing. Handshake helpers in the tests now
send the version header a real client sends.

Upstream reference: laravel/reverb main at 74c8c4082c.

Validation: the changed test files, the WebSocketServer, Sentry,
Foundation HTTP and Reverb unit and integration suites, formatting and
PHPStan pass.
ClientEventTest's unsupported-message test expected
hydratedConnections() never to be called, but the channel connection
manager has no such method, so the expectation could never fail.
laravel/reverb PR 407 asserts all() is never called instead, and the
test now does the same.

PR 407 converts upstream's tests from Mockery to Double. Hypervel keeps
Mockery and doesn't port the exact call counts, which only constrain
internal lookups. It also keeps the all() and find() stubs that PR 407
removed from the members-mode and none tests: Double fails on unused
expectations, while Mockery's spy allows them, and the stubs are what
let those tests' assertNothingReceived() checks catch a forwarded
message.

Upstream reference: laravel/reverb main at 74c8c4082c.

Validation: the changed test file and the Reverb unit suite pass.
Signed HTTP API requests were accepted whatever their auth_timestamp,
so a captured request could be replayed indefinitely. Following
laravel/reverb commit 2f8a121813, the controller now rejects a request
whose timestamp is missing or more than 600 seconds from the current
time, after the signature itself is verified. Hypervel passes the
verified query to the check instead of reading it from controller
state.

The signed-request test helpers take upstream's optional timestamp.
ChannelsControllerTest ports the expired and future rejection test and
adds a correctly signed request without a timestamp.

Upstream reference: laravel/reverb main at 74c8c4082c.

Validation: the changed test files, the Reverb unit and integration
suites, formatting and PHPStan pass.
laravel/reverb PR 408 fixes presence channels when scaling is enabled.
Most of it is already part of Hypervel's design: member events are
routed as internal events, so other servers don't cache them;
SharedState atomically decides each user's first and last connection
across workers and servers; and presence data is gathered from every
worker or server and merged into one unique list. Upstream's
presence_connections metric, subscription timestamps and prefix-based
internal routing have no Hypervel counterpart.

Two fixes are ported into the subscription's presence data:

- A member without user_info made the subscription fail with an
  ErrorException, and a member whose user_info was {} was sent as [],
  because subscription data is decoded as an array. Both are now sent
  as {}, as upstream's merge does.
- A failed presence gather propagated after the subscription was
  committed, so the client never got its subscription_succeeded. The
  failure is now reported and the subscription is answered with this
  worker's members. Cancellation still propagates.

Tests ported: the no-user-info data test (with an empty user_info as
well), the merged and unknown-channel presence cases in
MetricsHandlerTest using Hypervel's snapshot payloads, the presence
cache test for internal events, the findOrCreate change, and the three
Redis scaling tests in RedisServerTest, with member_removed merged into
the existing member notification test. EventHandlerTest covers the
gather fallback and cancellation.

Upstream reference: laravel/reverb main at 74c8c4082c.

Validation: the changed test files, the Reverb unit and integration
suites, formatting and PHPStan pass.
Hypervel's Typesense engine already defaults the import action to
upsert and passes the configured action to the import, as laravel/scout
#955 does, but only the default was tested. The partial-engine test
helper now takes config values; its config stub previously returned the
default for every key. TypesenseEngineTest ports upstream's emplace
action test.

Upstream reference: laravel/scout 11.x at ce2542f5a7.

Validation: TypesenseEngineTest, formatting and PHPStan pass.
Hypervel's Scout jobs already read their retry, backoff and exception
limits from config, fail on timeout by default and support unique
indexing, but some of upstream's job tests were missing.

RemoveFromSearchTest ports the no-config, timeout default and timeout
opt-out tests from laravel/scout #962 and #1002, replacing timeout
assertions folded into the config tests. From #996 it ports the exact
unique ID test, adapted to Hypervel's sha256 key, and the
different-models test; the existing order test takes upstream's name.
MakeSearchableTest gains the different-models test.

Upstream reference: laravel/scout 11.x at ce2542f5a7.

Validation: both test files, formatting and PHPStan pass.
Hypervel's Builder and engines already support where() with a
comparison operator, as laravel/scout #969 added, but its per-operator
tests were missing. DatabaseEngineTest ports the >, <, >=, <= and !=
tests, and its existing same-field comparison test takes upstream's
name.

The Meilisearch, Typesense and Algolia filtering integration tests ran
one combined > and != query. They now run upstream's shared comparison
cases, including the fixes from #976 and #978, which filter a typed
numeric field and make the two-result assertions order-independent.
Typesense filters its int32 ranking field, since its id is a string,
and Algolia keeps its escaped-string case.

Upstream reference: laravel/scout 11.x at ce2542f5a7.

Validation: DatabaseEngineTest and the Meilisearch and Typesense
filtering integration tests pass against those services; the Algolia
integration test was not run locally because no Algolia credentials
are configured. Formatting and PHPStan pass.
laravel/scout #1011 tests that a collection search for null returns
every model. In Hypervel, search() and the Builder constructor only
accepted a string, so Model::search($request->query('q')) threw a
TypeError when the request had no q parameter. Both now accept a
nullable query, and the Builder stores null as an empty string so
engines keep receiving a string query.

The collection engine already treated only an empty string as an empty
query, so searches for "0" and whitespace worked. CollectionEngineTest
ports upstream's null, whitespace and paginate-for-zero tests, and the
existing zero test takes upstream's name. The same file also ports
#969's collection-engine >, <, >=, <= and != tests, and its existing
same-field comparison test takes upstream's name.

Upstream reference: laravel/scout 11.x at ce2542f5a7.

Validation: CollectionEngineTest, the Scout test suite, formatting and
PHPStan pass.
Hypervel's Algolia engine already compiles boolean and inequality
filters as laravel/scout #1005 does, but its test didn't cover boolean
values in whereIn() and whereNotIn(). The existing boolean and
inequality test now adds upstream's cases, asserting OR for inclusion
and AND for exclusion with boolean literals.

Upstream reference: laravel/scout 11.x at ce2542f5a7.

Validation: AlgoliaEngineTest, formatting and PHPStan pass.
The Scout README listed internal changes, enhancements and fixes as
differences, missed coroutine-local sync pausing, and didn't follow the
package README layout.

It now links the documentation and lists only the public differences
that ported Laravel code has to account for: Algolia 4 only, numeric
Algolia filter values, indexing without a queue, coroutine-local
search-sync pausing, the tenant token method's arguments and the
prefix requirement for deleting all indexes.

Upstream reference: laravel/scout 11.x at ce2542f5a7.
@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: hypervel/components-backup/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: e2bae1b2-17f4-4d11-8fd5-8462e54aed47

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Sync Reverb and Scout fixes with upstream coverage

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Reject unsupported WebSocket versions and stale signed API requests to close protocol and replay
 gaps.
• Confirm presence subscriptions after distributed gather failures and serialize missing member
 details correctly.
• Accept null Scout queries and expand upstream-aligned search, scaling, and job tests.
• Clarify Scout’s Hypervel-specific behavior and link its documentation.
Diagram

graph TD
  WC["WebSocket Client"] --> HS["Handshake Server"] --> PC["Presence Channel"] --> MH["Metrics Handler"]
  HC["HTTP Client"] --> API["Signed Reverb API"]
  SM["Searchable Model"] --> SB["Scout Builder"]
Loading
High-Level Assessment

Keep the upstream-aligned changes within the existing handshake, signature-verification, presence, and Scout builder paths. Separate middleware or a new presence aggregation mechanism would add indirection without improving these focused fixes.

Files changed (28) +636 / -82

Bug fix (7) +67 / -13
InteractsWithPresenceChannels.phpConfirm presence subscriptions when member gathering fails +27/-6

Confirm presence subscriptions when member gathering fails

• Reports gather failures and falls back to unique members on the current worker after a subscription has committed, while allowing coroutine cancellation to propagate. Serializes missing or empty user information as an object.

src/reverb/src/Protocols/Pusher/Channels/Concerns/InteractsWithPresenceChannels.php

Controller.phpEnforce a 600-second signed-request timestamp window +21/-0

Enforce a 600-second signed-request timestamp window

• Rejects signed API requests with missing, nonnumeric, expired, or excessively future auth timestamps after verifying their signatures.

src/reverb/src/Protocols/Pusher/Http/Controllers/Controller.php

Builder.phpNormalize null search queries +2/-2

Normalize null search queries

• Accepts a nullable query in the builder constructor and stores null as an empty string.

src/scout/src/Builder.php

SearchableInterface.phpPermit nullable queries in the search contract +1/-1

Permit nullable queries in the search contract

• Changes the search method signature to accept a nullable string while retaining its empty-string default.

src/scout/src/Contracts/SearchableInterface.php

Searchable.phpAccept null at Scout’s search entry point +1/-1

Accept null at Scout’s search entry point

• Allows searchable models to pass a null query to the builder instead of raising a type error.

src/scout/src/Searchable.php

Security.phpDefine the WebSocket version header +2/-0

Define the WebSocket version header

• Adds a shared header-name constant for handshake version validation and test requests.

src/websocket-server/src/Security.php

Server.phpReject unsupported WebSocket versions before routing +13/-3

Reject unsupported WebSocket versions before routing

• Validates the requested WebSocket version after the security key. Returns HTTP 426 with upgrade and supported-version headers when it is not version 13.

src/websocket-server/src/Server.php

Tests (20) +561 / -60
RedisServerTest.phpCover presence behavior under Redis scaling +62/-0

Cover presence behavior under Redis scaling

• Adds checks for member removal, existing members in subscription confirmations, and exclusion of internal member events from presence-cache payloads.

tests/Integration/Reverb/RedisServerTest.php

ServerTest.phpExercise rejected WebSocket handshakes end to end +36/-0

Exercise rejected WebSocket handshakes end to end

• Verifies HTTP 400 for an invalid key and HTTP 426 for an unsupported version using clients with explicit upgrade headers and timeouts.

tests/Integration/Reverb/ServerTest.php

AlgoliaFilteringIntegrationTest.phpExpand Algolia comparison-filter integration coverage +13/-13

Expand Algolia comparison-filter integration coverage

• Tests individual comparison operators and combined numeric bounds against two indexed models, while retaining escaped-string coverage.

tests/Integration/Scout/Algolia/AlgoliaFilteringIntegrationTest.php

MeilisearchFilteringIntegrationTest.phpExpand Meilisearch comparison-filter integration coverage +12/-8

Expand Meilisearch comparison-filter integration coverage

• Replaces one combined comparison query with cases for greater-than, less-than, inclusive, inequality, and bounded filters.

tests/Integration/Scout/Meilisearch/MeilisearchFilteringIntegrationTest.php

TypesenseFilteringIntegrationTest.phpExpand Typesense numeric-filter integration coverage +12/-8

Expand Typesense numeric-filter integration coverage

• Tests comparison operators and combined bounds against the typed numeric ranking field, accepting either result order where both models match.

tests/Integration/Scout/Typesense/TypesenseFilteringIntegrationTest.php

PresenceCacheChannelTest.phpVerify internal presence events are not cached +35/-1

Verify internal presence events are not cached

• Checks that subscription activity, a dispatched internal member event, and unsubscription leave the presence-cache payload empty.

tests/Reverb/Protocols/Pusher/Channels/PresenceCacheChannelTest.php

PresenceChannelTest.phpCover missing and empty presence user information +20/-0

Cover missing and empty presence user information

• Verifies that both absent and empty user information serialize as empty JSON objects in the presence hash.

tests/Reverb/Protocols/Pusher/Channels/PresenceChannelTest.php

ClientEventTest.phpMake the unsupported-event assertion effective +1/-1

Make the unsupported-event assertion effective

• Asserts that unsupported client events never call the channel manager’s existing all method, replacing an expectation on a nonexistent method.

tests/Reverb/Protocols/Pusher/ClientEventTest.php

EventHandlerTest.phpTest presence gather failure and cancellation +59/-0

Test presence gather failure and cancellation

• Verifies that gather failures are reported and answered with local members. Separately verifies that coroutine cancellation propagates without a subscription response.

tests/Reverb/Protocols/Pusher/EventHandlerTest.php

ChannelsControllerTest.phpCover stale, future, and missing signature timestamps +30/-0

Cover stale, future, and missing signature timestamps

• Adds signed-request tests asserting HTTP 401 for timestamps outside the allowed window and for a correctly signed request lacking a timestamp.

tests/Reverb/Protocols/Pusher/Http/Controllers/ChannelsControllerTest.php

MetricsHandlerTest.phpStrengthen distributed presence snapshot assertions +28/-8

Strengthen distributed presence snapshot assertions

• Checks merged, deduplicated members across scaled responses, including mixed string and integer user IDs and empty user information. Also covers a channel absent from every server.

tests/Reverb/Protocols/Pusher/MetricsHandlerTest.php

ReverbTestCase.phpAllow explicit timestamps in signed-request helpers +6/-3

Allow explicit timestamps in signed-request helpers

• Adds optional timestamp arguments to signed GET and POST helpers so authentication tests can generate signatures for chosen times.

tests/Reverb/ReverbTestCase.php

CollectionEngineTest.phpCover collection comparisons and search edge cases +79/-2

Cover collection comparisons and search edge cases

• Adds individual comparison-operator cases, null and whitespace queries, and pagination for a string-zero search.

tests/Scout/Feature/CollectionEngineTest.php

DatabaseEngineTest.phpCover database engine comparison operators +52/-1

Cover database engine comparison operators

• Adds separate greater-than, less-than, inclusive, and inequality cases while retaining the multiple-comparison test.

tests/Scout/Feature/DatabaseEngineTest.php

AlgoliaEngineTest.phpAssert boolean set filters sent to Algolia +4/-2

Assert boolean set filters sent to Algolia

• Extends the expected filter expression and builder setup to include boolean whereIn and whereNotIn cases.

tests/Scout/Unit/Engines/AlgoliaEngineTest.php

TypesenseEngineTest.phpTest configurable Typesense emplace imports +25/-8

Test configurable Typesense emplace imports

• Adds an import test for the emplace action and makes the partial-engine config stub return supplied values.

tests/Scout/Unit/Engines/TypesenseEngineTest.php

MakeSearchableTest.phpDistinguish unique indexing jobs by model set +8/-0

Distinguish unique indexing jobs by model set

• Adds an assertion that indexing jobs containing different models produce different unique IDs.

tests/Scout/Unit/Jobs/MakeSearchableTest.php

RemoveFromSearchTest.phpExpand removal-job configuration and uniqueness tests +45/-4

Expand removal-job configuration and uniqueness tests

• Separately verifies unset retry properties, default timeout failure, subclass opt-out, and deterministic SHA-256 IDs that distinguish model sets.

tests/Scout/Unit/Jobs/RemoveFromSearchTest.php

WebSocketRuntimeContextTest.phpSend a valid version in Sentry handshake fixtures +1/-0

Send a valid version in Sentry handshake fixtures

• Adds the supported WebSocket version header to fixture requests so runtime-context tests reach their intended handshake paths.

tests/Sentry/WebSocketRuntimeContextTest.php

ServerHandshakeTest.phpVerify unsupported versions fail before routing +33/-1

Verify unsupported versions fail before routing

• Asserts the HTTP 426 response headers, absence of router dispatch, and connection-context cleanup. Updates handshake fixtures to send a configurable version.

tests/WebSocketServer/ServerHandshakeTest.php

Documentation (1) +8 / -9
README.mdClarify Scout differences from Laravel +8/-9

Clarify Scout differences from Laravel

• Replaces the broad differences list with Hypervel-specific behavior, including coroutine-local syncing pauses, and links the relevant documentation.

src/scout/README.md

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Presence members lose falsy profile data 🐞 Bug ≡ Correctness
Description
The data() method replaces every falsy user_info value with an empty object when building the
presence hash. A signed subscription containing user_info set to false, 0, or an empty string
is accepted and stored, but other subscribers receive {} instead of that value.
Code

src/reverb/src/Protocols/Pusher/Channels/Concerns/InteractsWithPresenceChannels.php[198]

+                'hash' => $connections->pluck('user_info', 'user_id')->map(fn ($info) => $info ?: (object) [])->all(),
Evidence
decodeSubscriptionData() accepts the decoded signed JSON without restricting user_info, and
subscribeToChannel() stores those attributes. The previous collection conversion preserved scalar
values; the new truthiness check replaces them before the response is serialized.

src/reverb/src/Protocols/Pusher/Channels/Channel.php[103-132]
src/reverb/src/Protocols/Pusher/Channels/Concerns/InteractsWithPresenceChannels.php[195-198]
src/collections/src/Traits/EnumeratesValues.php[936-943]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new presence-hash mapping turns valid falsy `user_info` values into `{}`.
## Fix Focus Areas
- src/reverb/src/Protocols/Pusher/Channels/Concerns/InteractsWithPresenceChannels.php[195-198]
- tests/Reverb/Protocols/Pusher/Channels/PresenceChannelTest.php[104-122]
## Recommended Fix
Convert only missing (`null`) and empty-array profile values to an empty object. Preserve other values, and add a subscription-response test for a falsy profile value.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@greptile-apps

greptile-apps Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[High risk] Updates WebSocket handshake validation and search builder signatures.

The PR appears safe to merge; no outstanding previous finding or actionable new issue was identified.

Summary

The PR syncs Reverb handshake, signature-timestamp and presence behavior with upstream, and expands Scout search and filtering coverage. Since the previous review, it has added the live handshake response-header assertion, clarified the Scout README, and adjusted timing-sensitive integration tests.

Reviews (2) · Last reviewed commit: "Correct the Scout README's tenant token ..."

Comment thread tests/Integration/Reverb/ServerTest.php
@greptile-apps

greptile-apps Bot commented Oct 2, 2026

Copy link
Copy Markdown

Comments Outside Diff

These findings could not be posted inline.

  • P1 Null fails in custom builders src/scout/src/Searchable.php:250 ▶

    If a model uses a custom Scout builder whose constructor still requires string $query, Model::search(null) passes null to it and throws a TypeError. That constructor was compatible before this change, so the new null-search support remains incomplete. Convert null to an empty string before resolving the builder.

                'query' => $query ?? '',
    

Two timing assertions occasionally failed in the Redis Cluster CI runs.

The funnel lease refresh test slept 1.1 seconds after acquiring a
three-second lease, then expected the refreshed lifetime to be longer
than the remaining one. The database and file stores report lifetimes
in whole seconds, rounding the deadline up and the current time down,
so when a second ended between the two reads both values could be 3.
Sleeping 2.1 seconds leaves at most 2 seconds before the refresh and at
least 3 after it.

The duration limiter's window runs from the current whole second to
that second plus the decay, while each attempt is compared with the
fractional current time. A one-second window opened late in a second
only lasts for the rest of that second, so an immediate second attempt
could land in a new window and succeed. This is normal fixed-window
behavior, shared with Laravel's limiter, so the limiter is unchanged.
The two tests that expect an immediate second attempt in a one-second
window to fail now start just after a whole second.

Validation: DurationLimiterIntegrationTest and the cache funnel tests
pass against Redis.
The live Reverb server test for an unsupported WebSocket version only
checked the 426 status. A running Reverb server renders handshake
errors through the application's exception handler, while the unit
tests that check the Sec-WebSocket-Version header cover the WebSocket
server's own handler. The live test now also asserts the
Sec-WebSocket-Version: 13 header that RFC 6455 requires on this
response, so the production path is covered.

Upstream reference: laravel/reverb main at 74c8c4082c.

Validation: ServerTest passes against a Reverb test server.
The entry left out the search rules when describing the Meilisearch
client's generateTenantToken() and didn't mention the engine's optional
expiry. It now lists the arguments of both methods: Hypervel's engine
takes the search rules, the parent key's UID, the key and an optional
expiry, while Laravel's engine forwards the call to the client, whose
method takes the UID, the search rules and an options array.

Upstream reference: laravel/scout 11.x at ce2542f5a7.
@binaryfire
binaryfire merged commit eb42532 into 0.4 Oct 2, 2026
53 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