Skip to content

[Bugfix] Move ScoutApm instrumentation into an initializer#2442

Open
tonyta wants to merge 1 commit intoshakacode:masterfrom
tonyta:mv-scout-apm-instrumentation-to-initializer
Open

[Bugfix] Move ScoutApm instrumentation into an initializer#2442
tonyta wants to merge 1 commit intoshakacode:masterfrom
tonyta:mv-scout-apm-instrumentation-to-initializer

Conversation

@tonyta
Copy link

@tonyta tonyta commented Feb 18, 2026

Summary

This PR fixes the load-order bug between ReactOnRails and ScoutApm that causes noisy output or prevents ReactOnRails's methods to be properly instrumented. The fix is to move instrumentation from gem load to Rails initializers.

Before:

Screenshot 2026-02-17 at 8 40 08 PM

After:

Screenshot 2026-02-17 at 8 40 23 PM

Details

edd6330 added ScoutApm instrumentation to methods on the following:

  • ReactOnRails::Helper
  • ReactOnRails::ServerRenderingPool::RubyEmbeddedJavaScript

Previously, this instrumentation was installed at gem load time using defined?(ScoutApm) guards. This caused two problems:

  1. Gem ordering dependency: if scout_apm appeared after react_on_rails in the Gemfile, ScoutApm was not yet defined when react_on_rails loaded, so instrumentation was silently skipped.

  2. Noisy log output: if scout_apm appeared before react_on_rails, instrumentation was installed before ScoutApm was initialized (via its "scout_apm.start" Railtie initializer), causing INFO-level messages to be written to stdout during boot.

This commit moves the instrumentation into ReactOnRails::Engine with an initializer explicitly configured to run after ScoutApm initializes ("scout_apm.start"). This ensures instrumentation is properly installed regardless of gem ordering and only after ScoutApm is fully configured.

Reproduction

A minimal reproduction of the bug can be found here: https://github.com/tonyta/react-on-rails-scout-apm-repro

It contains all the instructions to recreate the original issue and prove the fix.

Summary by CodeRabbit

  • Bug Fixes

    • Monitoring instrumentation now initializes reliably after the monitoring system starts, avoiding load-order issues.
    • Fixed Node renderer race conditions: per-request upload isolation to prevent file collisions/corruption and aligned locking to prevent concurrent render/upload conflicts; parallel bundle processing made safer.
  • Tests

    • Added tests validating the instrumentation initializer behavior with and without the monitoring system.
  • Chores

    • CI now recognizes and triggers Pro Node Renderer test runs when relevant changes are detected.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Moved ScoutApm instrumentation from gem-load-time guards into a Rails initializer react_on_rails.scout_apm_instrumentation that runs after scout_apm.start. Also fixed Node renderer upload and bundle race conditions by isolating per-request uploads and aligning bundle locks; added CI flagging for pro node renderer changes.

Changes

Cohort / File(s) Summary
ScoutApm Initializer
react_on_rails/lib/react_on_rails/engine.rb
Added initializer "react_on_rails.scout_apm_instrumentation", after: "scout_apm.start" to apply instrumentation at runtime when ScoutApm is present.
Instrumentation Removal
react_on_rails/lib/react_on_rails/helper.rb, react_on_rails/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb
Removed inline defined?(ScoutApm) blocks, include ScoutApm::Tracer, and instrument_method calls so instrumentation is handled by the new initializer.
Engine Specs
react_on_rails/spec/react_on_rails/engine_spec.rb
Added specs for the new initializer verifying behavior with and without a mock ScoutApm::Tracer (instrumented vs uninstrumented method aliases).
Node renderer — per-request uploads
(Node renderer files) node_renderer/...
Isolated each request upload into its own uploads/<uuid>/ directory to prevent ENOENT and asset-corruption races during concurrent uploads.
Node renderer — bundle locking & processing
(Node renderer files) node_renderer/...
Aligned locking by using the same per-bundle lock key for /upload-assets and render endpoints; switched parallel bundle processing to Promise.allSettled to avoid cleanup races while copies are in-flight.
CI change detector
script/ci-changes-detector
Added PRO_NODE_RENDERER_CHANGED flag handling, exported run_pro_node_renderer_tests, and updated CI outputs/logic to trigger pro node renderer test runs for relevant changes.
CHANGELOG
CHANGELOG.md
Recorded fixes: ScoutApm instrumentation no longer depends on Gemfile order; node renderer upload and bundle race condition fixes.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant AppServer as Rails App Server
    participant NodeRenderer as Node Renderer
    participant FS as Filesystem
    participant LockService as Bundle Lock

    Client->>AppServer: POST /upload-assets (with bundle)
    AppServer->>NodeRenderer: stream upload -> writes to uploads/<uuid>/
    NodeRenderer->>FS: write to uploads/<uuid>/bundle.tmp
    NodeRenderer->>LockService: acquire lock(bundle)
    NodeRenderer->>FS: move/copy from uploads/<uuid>/ to bundle-dir
    LockService-->>NodeRenderer: release lock(bundle)

    Client->>AppServer: POST /render (concurrent)
    AppServer->>LockService: acquire lock(bundle)
    LockService-->>AppServer: lock granted
    AppServer->>FS: read from bundle-dir (safe, isolated)
    AppServer->>NodeRenderer: render using bundle
    NodeRenderer->>FS: read assets from bundle-dir
    AppServer->>LockService: release lock(bundle)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇
I nudged the init so scouts wake on time,
Each upload gets a burrow, tidy and prime.
Locks clap softly, bundles race no more,
Tests wake the CI, then hop to the door,
I nibble a carrot — fixed and sublime. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix: moving ScoutApm instrumentation from gem-load time to a Rails initializer, which directly addresses the primary changeset across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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 and usage tips.

@tonyta tonyta force-pushed the mv-scout-apm-instrumentation-to-initializer branch 2 times, most recently from 788e38b to 0cef7e6 Compare February 18, 2026 04:52
@greptile-apps
Copy link

greptile-apps bot commented Feb 18, 2026

Greptile Summary

This PR fixes a load-order bug between ReactOnRails and ScoutApm by moving APM instrumentation from gem load time into a Rails initializer that runs after scout_apm.start.

  • Centralized instrumentation: Moved ScoutApm instrument_method calls from inline defined?(ScoutApm) guards in Helper and RubyEmbeddedJavaScript into a single initializer in ReactOnRails::Engine, configured with after: "scout_apm.start"
  • Eliminated gem ordering dependency: Previously, ScoutApm instrumentation was silently skipped if scout_apm appeared after react_on_rails in the Gemfile, and produced noisy INFO log output if it appeared before
  • Comprehensive test coverage: Added tests verifying initializer ordering, instrumentation when ScoutApm is present, and no-op behavior when ScoutApm is absent, using a well-crafted mock of ScoutApm::Tracer
  • Minor: CHANGELOG entry contains placeholder PR XXXX that should be replaced with the actual PR number (2442)

Confidence Score: 5/5

  • This PR is safe to merge — it fixes a real bug with a clean, well-tested approach and has no risk of regression.
  • The change is straightforward and correct: it moves existing instrumentation logic from gem load time to a Rails initializer with proper ordering. The defined?(ScoutApm) guard ensures safety when ScoutApm is not present, and the after: "scout_apm.start" ensures proper initialization order. Tests are thorough and the only actionable item is updating the placeholder PR number in the changelog.
  • CHANGELOG.md needs the placeholder PR number (XXXX) replaced with the actual PR number (2442).

Important Files Changed

Filename Overview
react_on_rails/lib/react_on_rails/engine.rb Adds a new Rails initializer that installs ScoutApm instrumentation after "scout_apm.start", with a defined?(ScoutApm) guard. Clean and correct implementation.
react_on_rails/lib/react_on_rails/helper.rb Removes the inline defined?(ScoutApm) guard and instrumentation code that was previously executed at gem load time. Clean removal, no residual artifacts.
react_on_rails/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb Removes the inline defined?(ScoutApm) guard and instrumentation code from the class-level context. Clean removal, no residual artifacts.
react_on_rails/spec/react_on_rails/engine_spec.rb Adds thorough tests for the ScoutApm initializer: verifies ordering config, tests both ScoutApm-present and ScoutApm-absent scenarios using a well-crafted mock tracer.
CHANGELOG.md Adds changelog entry for the ScoutApm fix. Contains placeholder "PR XXXX" that needs to be replaced with the actual PR number before merge.

Sequence Diagram

sequenceDiagram
    participant Rails as Rails Boot
    participant Scout as ScoutApm Railtie
    participant RoR as ReactOnRails Engine

    Rails->>Scout: Run scout_apm.start initializer
    Scout->>Scout: Initialize ScoutApm agent and config
    Rails->>RoR: Run react_on_rails.scout_apm_instrumentation
    RoR->>RoR: Check defined?(ScoutApm)
    alt ScoutApm is loaded
        RoR->>RoR: Instrument Helper methods
        RoR->>RoR: Instrument RubyEmbeddedJavaScript methods
    else ScoutApm not loaded
        RoR->>RoR: Skip instrumentation
    end
Loading

Last reviewed commit: 0cef7e6

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@tonyta tonyta changed the title Move ScoutApm instrumentation into an initializer [Bugfix] Move ScoutApm instrumentation into an initializer Feb 18, 2026
@tonyta tonyta force-pushed the mv-scout-apm-instrumentation-to-initializer branch from 0cef7e6 to 43fe65d Compare February 18, 2026 05:04
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
react_on_rails/spec/react_on_rails/engine_spec.rb (2)

210-228: "When ScoutApm is not defined" context is fragile without hide_const.

These tests rely implicitly on ScoutApm being absent from the test environment. If scout_apm is ever added as a test dependency (or is transiently defined in the test process), defined?(ScoutApm) will be truthy and the initializer will run instrumentation, flipping all three assertions from green to red without any obvious cause.

Use hide_const to make the precondition explicit and robust:

🛡️ Suggested fix
         context "when ScoutApm is not defined" do
+          before { hide_const("ScoutApm") }
+
           it "does not instrument Helper#react_component" do
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/spec/react_on_rails/engine_spec.rb` around lines 210 - 228,
The "when ScoutApm is not defined" examples assume ScoutApm is absent; make this
explicit by using hide_const("ScoutApm") in that context so the tests are
robust; update the context block (surrounding initializer.run and expectations
for mock_helper and mock_rb_embedded_js) to call hide_const('ScoutApm') (or
equivalent RSpec helper) before running initializer.run to ensure ScoutApm is
undefined for the three specs that check react_component, react_component_hash,
and exec_server_render_js instrumentation.

170-170: Consider using exact name match in the subject finder.

name.include?("scout_apm") would inadvertently match a second initializer if one were ever added (e.g., react_on_rails.scout_apm_something_else). A precise match is more defensive:

♻️ Suggested fix
-      subject(:initializer) { described_class.initializers.find { |i| i.name.include?("scout_apm") } }
+      subject(:initializer) { described_class.initializers.find { |i| i.name == "react_on_rails.scout_apm_instrumentation" } }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/spec/react_on_rails/engine_spec.rb` at line 170, The subject
finder uses a substring match which can accidentally select multiple
initializers; update the finder that defines subject(:initializer) to locate the
initializer by exact name against described_class.initializers (replace the
.name.include?("scout_apm") check with an exact equality check against the known
initializer name, e.g., .name == "react_on_rails.scout_apm") so only the
intended initializer is selected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@react_on_rails/spec/react_on_rails/engine_spec.rb`:
- Around line 210-228: The "when ScoutApm is not defined" examples assume
ScoutApm is absent; make this explicit by using hide_const("ScoutApm") in that
context so the tests are robust; update the context block (surrounding
initializer.run and expectations for mock_helper and mock_rb_embedded_js) to
call hide_const('ScoutApm') (or equivalent RSpec helper) before running
initializer.run to ensure ScoutApm is undefined for the three specs that check
react_component, react_component_hash, and exec_server_render_js
instrumentation.
- Line 170: The subject finder uses a substring match which can accidentally
select multiple initializers; update the finder that defines
subject(:initializer) to locate the initializer by exact name against
described_class.initializers (replace the .name.include?("scout_apm") check with
an exact equality check against the known initializer name, e.g., .name ==
"react_on_rails.scout_apm") so only the intended initializer is selected.

@tonyta tonyta force-pushed the mv-scout-apm-instrumentation-to-initializer branch from 43fe65d to 20a0ba4 Compare February 18, 2026 05:16
@tonyta
Copy link
Author

tonyta commented Feb 18, 2026

Changes since initial push (via amend and force-push):

  • replaced placeholder PR number with this one
  • fixed formatting on CHANGELOG.md entry
  • added link to scout_apm.start initializer source
  • hide_const for "when ScoutApm is not defined" test case

@justin808
Copy link
Member

@claude review this PR

@claude
Copy link

claude bot commented Feb 18, 2026

Claude Code is working…

I'll analyze this and get back to you.

View job run

@tonyta
Copy link
Author

tonyta commented Feb 18, 2026

@justin808 Claude Code stalled while attempting to pull my branch from origin.

Error: Action failed with error: Command failed: git fetch origin --depth=20 mv-scout-apm-instrumentation-to-initializer
Error: Process completed with exit code 1.

It issue seems to be that origin is configured to point to this repo, but my feature branch is pushed to my forked repo.

Initialized empty Git repository in /home/runner/work/react_on_rails/react_on_rails/.git/
/usr/bin/git remote add origin https://github.com/shakacode/react_on_rails

I can try pushing my feature branch to this repo instead and retrying the Github Action or recreate this PR with the feature branch in this repo instead.


Update: I was not able to push my feature branch to origin due to permissions (as expected).

$ git push origin mv-scout-apm-instrumentation-to-initializer

ERROR: Permission to shakacode/react_on_rails.git denied to tonyta.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

You can push the branch to this repo using following:

git remote add tonyta git@github.com:tonyta/react_on_rails.git
git fetch tonyta mv-scout-apm-instrumentation-to-initializer
git push origin refs/remotes/tonyta/mv-scout-apm-instrumentation-to-initializer:refs/heads/mv-scout-apm-instrumentation-to-initializer

Note: This will allow Claude Code review the same commit but technically the reviewed branch is distinct from the source branch to be merged.

@justin808
Copy link
Member

@tonyta Looks good. Can you resolve the changelog conflict, or let me know and I'll create a new branch and do it.

@justin808
Copy link
Member

@claude review this PR

@claude
Copy link

claude bot commented Feb 19, 2026

Claude finished @justin808's task in 1m 37s —— View job


PR Review: Move ScoutApm Instrumentation to Initializer

Tasks:

  • Gather context and understand request
  • Read changed files
  • Review implementation
  • Post review feedback

This is a clean, well-motivated fix. The root cause analysis is solid and the approach is idiomatic for Rails engines. Here are my findings:

Summary

Verdict: Approve with minor notes. The implementation is correct, the tests are comprehensive, and the most important CodeRabbit nitpick (hide_const) has already been applied (line 211 of engine_spec.rb). The PR is ready to merge after the CHANGELOG conflict is resolved.


Implementation (engine.rb:88-101)

The initializer structure is correct:

initializer "react_on_rails.scout_apm_instrumentation", after: "scout_apm.start" do
  next unless defined?(ScoutApm)
  ...
end
  • after: "scout_apm.start" is the right hook point — Rails silently ignores the after: dependency when the referenced initializer doesn't exist, so this is safe whether or not scout_apm is in the Gemfile.
  • The next unless defined?(ScoutApm) guard correctly handles the no-ScoutApm case.
  • Using singleton_class.class_eval for RubyEmbeddedJavaScript (line 97) is correct since exec_server_render_js is defined inside class << self.

One subtle consideration: The guard checks defined?(ScoutApm) but then immediately references ScoutApm::Tracer. In a non-standard or partial ScoutApm installation where ScoutApm is defined but ScoutApm::Tracer is not, this would raise NameError. The original code had the same pattern, so this isn't a regression, but defined?(ScoutApm::Tracer) would be more precise. This is a very edge case and not a blocker.

Tests (engine_spec.rb)

The test design is solid:

  • Line 170: The initializer finder still uses a substring match (i.name.include?("scout_apm")). CodeRabbit flagged this — an exact match is more defensive. Fix this →

  • Lines 210-229: hide_const("ScoutApm") is correctly applied (already fixed from initial review). ✓

  • Lines 232-252: stub_const("ScoutApm::Tracer", mock_scout_tracer) correctly causes defined?(ScoutApm) to be truthy (RSpec creates the namespace when stubbing a nested constant). ✓

  • Line 202-203: Mocking via Module.new.include(ReactOnRails::Helper) and Class.new(RubyEmbeddedJavaScript) combined with stub_const is the right approach — it avoids mutating the real classes across tests. ✓

  • The mock ScoutApm::Tracer accurately mirrors the real tracer's alias-chain behavior, making the instrumentation detection meaningful. ✓

CHANGELOG

The entry is correct and the PR number placeholder has been replaced with 2442. ✓

Outstanding Issue

CHANGELOG merge conflict@justin808 mentioned there's a conflict to resolve. The [Unreleased] section will need to be rebased on master before merging. @tonyta: if you'd like me to handle the rebase, let me know.

@tonyta
Copy link
Author

tonyta commented Feb 19, 2026

@justin808 I've rebased and force-pushed to my source branch. You can verify the rebase was correctly applied with the following:

git fetch tonyta
diff -u \
  <(git show tonyta/mv-scout-apm-instrumentation-to-initializer) \
  <(git show tonyta/mv-scout-apm-instrumentation-to-initializer-before-rebase)

Update: I was able to trigger Github to recognize the new commit be rewriting the commit and force-pushing again. We are ready to merge! Thanks for the review!


(previously)

While the branch in my repo shows commit e974c8d6, Github hasn't picked up the changes in this PR for some reason, still showing it at commit 20a0ba43 and with a blocking merge conflict.

@justin808 feel free to resolve it on your end.

@tonyta tonyta force-pushed the mv-scout-apm-instrumentation-to-initializer branch from e974c8d to 9fcba5a Compare February 20, 2026 23:19
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
react_on_rails/lib/react_on_rails/engine.rb (1)

87-87: Optional: unpin the ScoutApm version reference in the comment.

The comment links directly to the v6.1.0 tag. As ScoutApm releases new versions, this link will silently point to old code. Linking to the main branch (or omitting the tag entirely) would keep the reference evergreen.

✏️ Suggested update
-    # https://github.com/scoutapp/scout_apm_ruby/blob/v6.1.0/lib/scout_apm.rb#L221
+    # https://github.com/scoutapp/scout_apm_ruby/blob/main/lib/scout_apm.rb
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/lib/react_on_rails/engine.rb` at line 87, The comment
currently references a version-tagged ScoutApm URL (v6.1.0); update the comment
in react_on_rails::engine.rb (the line with the ScoutApm link) to use an
evergreen link (e.g., the repository path without the tag or pointing to the
main branch) or remove the tag portion so the reference stays current as
ScoutApm releases new versions.
react_on_rails/spec/react_on_rails/engine_spec.rb (1)

170-170: Optional: prefer an exact name match in the subject finder.

The substring match works today but would silently latch onto a different initializer if another "scout_apm"-named initializer were added. An exact equality check is both more precise and more self-documenting.

✏️ Suggested update
-      subject(:initializer) { described_class.initializers.find { |i| i.name.include?("scout_apm") } }
+      subject(:initializer) { described_class.initializers.find { |i| i.name == "react_on_rails.scout_apm_instrumentation" } }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/spec/react_on_rails/engine_spec.rb` at line 170, The subject
finder uses a substring match on initializer names which can collide; update the
finder that locates the initializer (subject(:initializer) {
described_class.initializers.find { |i| i.name.include?("scout_apm") } }) to use
exact equality on the initializer name (compare i.name == "scout_apm" or
equivalent) so it selects the precise initializer by name rather than any name
that merely contains that substring.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@react_on_rails/lib/react_on_rails/engine.rb`:
- Line 87: The comment currently references a version-tagged ScoutApm URL
(v6.1.0); update the comment in react_on_rails::engine.rb (the line with the
ScoutApm link) to use an evergreen link (e.g., the repository path without the
tag or pointing to the main branch) or remove the tag portion so the reference
stays current as ScoutApm releases new versions.

In `@react_on_rails/spec/react_on_rails/engine_spec.rb`:
- Line 170: The subject finder uses a substring match on initializer names which
can collide; update the finder that locates the initializer
(subject(:initializer) { described_class.initializers.find { |i|
i.name.include?("scout_apm") } }) to use exact equality on the initializer name
(compare i.name == "scout_apm" or equivalent) so it selects the precise
initializer by name rather than any name that merely contains that substring.

@tonyta tonyta force-pushed the mv-scout-apm-instrumentation-to-initializer branch from 9fcba5a to 3c18116 Compare February 23, 2026 17:51
@tonyta
Copy link
Author

tonyta commented Feb 23, 2026

@justin808 I just rebase again due to the recurring merge conflict on CHANGELOG.md.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CHANGELOG.md (1)

30-36: ⚠️ Potential issue | 🟡 Minor

ScoutApm entry is misplaced in the #### Pro section.

The ScoutApm instrumentation fix lives entirely in the core gem (react_on_rails/lib/react_on_rails/engine.rb, helper.rb, server_rendering_pool/ruby_embedded_java_script.rb) — not in react_on_rails_pro/. It should be in a non-Pro #### Fixed section under ### [Unreleased].

🔧 Suggested placement
 ### [Unreleased]

 Changes since the last non-beta release.

+#### Fixed
+
+- **Fixed ScoutApm instrumentation depending on Gemfile ordering**. ... [PR 2442](https://github.com/shakacode/react_on_rails/pull/2442) by [tonyta](https://github.com/tonyta).
+
 #### Pro

 ##### Fixed

-- **Fixed ScoutApm instrumentation depending on Gemfile ordering**. ... [PR 2442](...) by [tonyta](...).
 - **Fixed node renderer upload race condition ...** [PR 2456](...) ...
 - **Fixed node renderer race condition ...** [PR 2464](...) ...

Based on learnings, "Pro-only changes go in the #### Pro section of /CHANGELOG.md; shared changes go in the main section only without duplication."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` around lines 30 - 36, Move the ScoutApm instrumentation bullet
out of the "#### Pro" section and into the main "#### Fixed" subsection under
"### [Unreleased]" (so it appears as a non‑Pro shared change). Update the
changelog entry text if needed to remove any "Pro" wording and ensure it
references the core files altered (react_on_rails/lib/react_on_rails/engine.rb,
helper.rb, and server_rendering_pool/ruby_embedded_java_script.rb) to make clear
this fix lives in the core gem rather than react_on_rails_pro.
🧹 Nitpick comments (1)
react_on_rails/spec/react_on_rails/engine_spec.rb (1)

169-175: Consider guarding against a nil subject.

find { |i| i.name.include?("scout_apm") } returns nil if no matching initializer is found. Failures would then surface as NoMethodError: undefined method 'name' for nil rather than a clear expectation failure. Adding an explicit non-nil assertion improves debuggability.

♻️ Proposed fix
 subject(:initializer) { described_class.initializers.find { |i| i.name.include?("scout_apm") } }

 it "defines a named Rails initializer to run after scout_apm.start" do
+  expect(initializer).not_to be_nil, "Expected to find a scout_apm initializer in ReactOnRails::Engine"
   expect(initializer.name).to eq "react_on_rails.scout_apm_instrumentation"
   expect(initializer.after).to eq "scout_apm.start"
 end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/spec/react_on_rails/engine_spec.rb` around lines 169 - 175,
The spec's subject(:initializer) can be nil if no matching initializer is found,
causing a NoMethodError; add an explicit non-nil assertion (e.g.,
expect(initializer).not_to be_nil) immediately after subject(:initializer) is
defined and before the existing expectations that access initializer.name and
initializer.after so the test fails with a clear assertion message rather than a
NoMethodError.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@CHANGELOG.md`:
- Around line 30-36: Move the ScoutApm instrumentation bullet out of the "####
Pro" section and into the main "#### Fixed" subsection under "### [Unreleased]"
(so it appears as a non‑Pro shared change). Update the changelog entry text if
needed to remove any "Pro" wording and ensure it references the core files
altered (react_on_rails/lib/react_on_rails/engine.rb, helper.rb, and
server_rendering_pool/ruby_embedded_java_script.rb) to make clear this fix lives
in the core gem rather than react_on_rails_pro.

---

Nitpick comments:
In `@react_on_rails/spec/react_on_rails/engine_spec.rb`:
- Around line 169-175: The spec's subject(:initializer) can be nil if no
matching initializer is found, causing a NoMethodError; add an explicit non-nil
assertion (e.g., expect(initializer).not_to be_nil) immediately after
subject(:initializer) is defined and before the existing expectations that
access initializer.name and initializer.after so the test fails with a clear
assertion message rather than a NoMethodError.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fcba5a and 3c18116.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • react_on_rails/lib/react_on_rails/engine.rb
  • react_on_rails/lib/react_on_rails/helper.rb
  • react_on_rails/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb
  • react_on_rails/spec/react_on_rails/engine_spec.rb
  • script/ci-changes-detector
💤 Files with no reviewable changes (2)
  • react_on_rails/lib/react_on_rails/helper.rb
  • react_on_rails/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • react_on_rails/lib/react_on_rails/engine.rb

@tonyta tonyta force-pushed the mv-scout-apm-instrumentation-to-initializer branch from 3c18116 to 1b1ddd2 Compare February 24, 2026 17:49
edd6330 added ScoutApm instrumentation to methods on the following:
- ReactOnRails::Helper
- ReactOnRails::ServerRenderingPool::RubyEmbeddedJavaScript

Previously, this instrumentation was installed at gem load time using
`defined?(ScoutApm)` guards. This caused two problems:

1. Gem ordering dependency: if scout_apm appeared after react_on_rails
   in the Gemfile, ScoutApm was not yet defined when react_on_rails
   loaded, so instrumentation was silently skipped.

2. Noisy log output: if scout_apm appeared before react_on_rails,
   instrumentation was installed before ScoutApm was initialized (via
   its "scout_apm.start" Railtie initializer), causing INFO-level
   messages to be written to stdout during boot.

This commit moves the instrumentation into ReactOnRails::Engine with
an initializer explicitly configured to run after ScoutApm initializes
("scout_apm.start"). This ensures instrumentation is properly installed
regardless of gem ordering and only after ScoutApm is fully configured.
@tonyta tonyta force-pushed the mv-scout-apm-instrumentation-to-initializer branch from 1b1ddd2 to 5f59369 Compare February 25, 2026 23:34
@tonyta tonyta requested a review from justin808 February 25, 2026 23:34
@tonyta
Copy link
Author

tonyta commented Feb 25, 2026

@justin808 I appreciate your attention here and hope we can merge this soon as I keep having to rebase to fix merge conflicts on CHANGELOG.md 😅

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