Skip to content

Fix webpack-dev-server static config: default to false, fix YAML passthrough#1032

Open
ihabadham wants to merge 8 commits intomainfrom
ihabadham/fix/static-config-bugs
Open

Fix webpack-dev-server static config: default to false, fix YAML passthrough#1032
ihabadham wants to merge 8 commits intomainfrom
ihabadham/fix/static-config-bugs

Conversation

@ihabadham
Copy link
Copy Markdown
Contributor

@ihabadham ihabadham commented Mar 30, 2026

Summary

Fixes three bugs in Shakapacker's webpack-dev-server static configuration that cause unnecessary inotify watches and ENOSPC crashes on systems with limited headroom.

  • Default static from a misconfigured object to false — Rails serves public/ via ActionDispatch::Static, webpack-dev-server doesn't need to
  • Fix if (devServerYamlConfig.static) truthy check so static: false in shakapacker.yml is no longer silently ignored
  • Remove static.watch from the default install template — it was a v3→v4 migration artifact that caused webpack-dev-server to watch public/ via chokidar
  • Pass through all valid webpack-dev-server static values (true, strings, arrays, objects) from YAML — if a user explicitly overrides the default, we respect it

Fixes #1031

Details

The old default set static.publicPath to a filesystem path (config.outputPath) instead of a URL path, and never set static.directory, causing webpack-dev-server to default to watching the entire public/ directory. This has been present since v6.0.0-rc.7 but was harmless on most systems. On systems near their inotify watch limit (e.g., Fedora with auto-calculated limits + watch-heavy tools like Warp terminal), it triggers ENOSPC: System limit for number of file watchers reached.

Setting static: false is safe because:

  • Shakapacker::DevServerProxy (hardwired in the railtie) proxies /packs/* to webpack-dev-server — the browser never accesses webpack-dev-server directly
  • Webpack bundles are served through devMiddleware, which is independent of static
  • ActionDispatch::Static serves public/ files in Rails

Users who explicitly set static in shakapacker.yml (to true, an object, a string path, etc.) will have their value passed through to webpack-dev-server as-is. Bare static: (YAML null) is treated the same as unset — the default false applies.

Test plan

  • Added 10 new tests in test/package/webpackDevServerConfig.test.js covering default behavior, false/true/string/object passthrough, and existing key mapping
  • All 13 relevant tests pass (10 new + 3 existing dev_server)
  • yarn lint — 0 errors
  • yarn type-check — clean

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

Walkthrough

Defaults and YAML handling for webpack-dev-server static were corrected: default static is now false, explicit static: false in shakapacker.yml is honored, static.watch was removed from the default template, and tests were added to validate passthrough and key mappings.

Changes

Cohort / File(s) Summary
Changelog & Default YAML
CHANGELOG.md, lib/install/config/shakapacker.yml
Added changelog entry; removed the dev_server.static.watch.ignored entry from the development shakapacker.yml template (removed default static.watch).
Dev Server Config Implementation
package/webpackDevServerConfig.ts
Changed WebpackDevServerConfig.static type to allow `boolean
Tests
test/package/webpackDevServerConfig.test.js
Added Jest tests verifying default static (false), YAML static passthrough for false/true/string/object, devMiddleware.publicPath, HMR/liveReload mapping, snake_case → camelCase key mapping, and client passthrough.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped through configs, sniffed the trail,
Where watchers peered in public vale,
I whispered "false" and cleared the way,
Now builds breathe easy, light of day,
A tiny carrot cheers the fix 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: fixing webpack-dev-server static config by defaulting to false and fixing YAML passthrough behavior.
Linked Issues check ✅ Passed The PR fully addresses all coding objectives from issue #1031: changes default static to false, implements proper YAML false passthrough, removes static.watch from config template, expands type definitions, and adds comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the three static configuration bugs identified in issue #1031 with no extraneous modifications.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ihabadham/fix/static-config-bugs

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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 30, 2026

Greptile Summary

This PR fixes three real bugs in Shakapacker's webpack-dev-server static configuration that could cause ENOSPC crashes on inotify-constrained systems. The core changes are: (1) defaulting static to false instead of a misconfigured object that triggered unnecessary public/ directory watching, (2) fixing the truthy-check guard so static: false in shakapacker.yml is no longer silently ignored, and (3) removing the stale static.watch block from the install template. The fix is well-motivated and the 8 new tests cover the main behavioral paths cleanly.

Key findings:

  • The CHANGELOG entry uses a placeholder [PR #XXXX] that should be updated to [PR #1032] before merge.
  • The new YAML passthrough logic only handles false and plain objects. Truthy non-object values — static: true, a string path, or an array — fall through both branches silently and leave config.static = false, which is the opposite of what a user specifying those values would expect. The WebpackDevServerConfig TypeScript type doesn't model these cases either, so the fix is incomplete for the full webpack-dev-server static API surface.

Confidence Score: 5/5

Safe to merge — the core bug fixes are correct and all remaining findings are P2 style/edge-case suggestions.

Both open findings are P2: the CHANGELOG placeholder is a trivial text fix, and the silent drop of non-object static values is an unlikely edge case that wasn't handled correctly in the old code either. The primary fix is sound, well-tested with 8 new tests, and addresses the real-world ENOSPC regression.

CHANGELOG.md (placeholder PR number) and package/webpackDevServerConfig.ts (incomplete passthrough for truthy non-object static values).

Important Files Changed

Filename Overview
package/webpackDevServerConfig.ts Defaults static to false, correctly handles false and object passthrough from YAML, but silently drops truthy non-object values (e.g. true, strings, arrays) leaving static: false with no warning.
test/package/webpackDevServerConfig.test.js New test file with 8 tests covering the default, false passthrough, object passthrough, devMiddleware.publicPath, hmr→hot mapping, liveReload default, snake_case→camelCase mapping, and client config passthrough — all using correct jest.resetModules() isolation pattern.
lib/install/config/shakapacker.yml Removes the static.watch.ignored block from the development section, eliminating the v3→v4 migration artifact that caused unnecessary inotify watches on public/.
CHANGELOG.md Adds a "Fixed" entry for the three bugs, but uses the placeholder #XXXX instead of the actual PR number #1032.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[createDevServerConfig called] --> B[Build base config\nstatic: false]
    B --> C{devServerYamlConfig.static\n!== undefined?}
    C -- No --> F[Keep static: false]
    C -- Yes --> D{static === false?}
    D -- Yes --> E[config.static = false]
    D -- No --> G{typeof static === 'object'\n&& static !== null?}
    G -- Yes --> H[config.static = static object]
    G -- No --> I[⚠️ Silently ignored\nconfig.static stays false\ne.g. static: true or string]
    E --> J[delete devServerYamlConfig.static]
    H --> J
    I --> J
    F --> K[Process remaining YAML keys\nsnake_case to camelCase]
    J --> K
    K --> L[Return WebpackDevServerConfig]
Loading

Reviews (1): Last reviewed commit: "docs: update changelog with PR number" | Re-trigger Greptile

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2645056893

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 30, 2026

Review

The fix is well-motivated and the root-cause analysis is solid. Defaulting static to false, fixing the truthy-check regression, and cleaning up the stale template entry are all correct and safe changes. The new test suite is thorough for the cases it covers.

One correctness issue to address:

static: true in shakapacker.yml is now silently discarded — see the inline comment on webpackDevServerConfig.ts. webpack-dev-server accepts static: true as a valid option (enables its built-in static-serving defaults), so silently ignoring it is a behavioral regression. Adding an else passthrough branch (and a matching test) would close the gap.

Everything else looks good:

  • Removing outputPath import is correct since the default static object that used it is gone.
  • The !== undefined guard properly catches static: false where the old truthy check didn't.
  • Test isolation via jest.resetModules() + mutating exports before requiring the config module under test is consistent with the project's existing patterns (e.g., dev_server.test.js).
  • YAML template cleanup is appropriate — the static.watch.ignored block was indeed a v3→v4 migration artifact.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 30, 2026

Review

The fix is well-motivated and the core change is correct: static: false is the right default for a Rails + Shakapacker setup where ActionDispatch::Static already serves public/ and DevServerProxy handles /packs/*. The truthy-check bug fix and template cleanup are also solid.

One real issue: static: true / string values are silently discarded

The new branch handles false and objects, but any other truthy value from YAML (webpack-dev-server accepts true, a string path, and arrays) falls through both inner conditionals, gets deleted, and silently leaves config.static = false. I've left an inline comment with a suggested fix (warn + keep false, or pass non-false primitives/arrays through).

A test covering that path would pin down the intended behavior — also noted inline.

Everything else looks good

  • Removing outputPath from the import is correct; it was never a valid publicPath value (filesystem path ≠ URL path).
  • The !== undefined guard properly handles the false passthrough that the old truthy check missed.
  • Test structure matches the existing dev_server.test.js pattern (chdirTestApp() at module scope + afterAll restore).
  • The jest.resetModules() + mutate-then-require pattern correctly exercises module-level require("./dev_server") in the production code.
  • CHANGELOG format and placement follow project conventions.

Copy link
Copy Markdown

@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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package/webpackDevServerConfig.ts`:
- Around line 104-114: The file is missing a trailing newline and the
devServerYamlConfig.static handling only accounts for false and object types;
update the code to add a final newline and to set config.static when
devServerYamlConfig.static is a string or array (e.g., if typeof === "string"
assign the string, if Array.isArray assign the array cast to Array<string |
Record<string, unknown>>), keeping the existing false and object branches and
still deleting devServerYamlConfig.static afterwards; also update the
WebpackDevServerConfig.static type definition (the static property in the config
interface) to include string and Array<string | object> variants so the types
align with webpack-dev-server API.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5796f134-083b-442b-855a-214624d3508d

📥 Commits

Reviewing files that changed from the base of the PR and between 4cd5250 and 2645056.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • lib/install/config/shakapacker.yml
  • package/webpackDevServerConfig.ts
  • test/package/webpackDevServerConfig.test.js
💤 Files with no reviewable changes (1)
  • lib/install/config/shakapacker.yml

ihabadham and others added 4 commits March 30, 2026 18:23
Documents expected behavior for static config: default to false,
pass through YAML static: false, and pass through static objects.
Some tests fail against current code — fixes follow in next commits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three bugs fixed:

1. Default static changed from { publicPath: contentBase } to false.
   The old default set static.publicPath to a filesystem path (config.outputPath)
   instead of a URL path, and never set static.directory, causing
   webpack-dev-server to watch the entire public/ directory unnecessarily.
   In Rails apps, static file serving is handled by ActionDispatch::Static,
   not webpack-dev-server.

2. The truthy check 'if (devServerYamlConfig.static)' now uses
   'if (devServerYamlConfig.static !== undefined)' so that setting
   static: false in shakapacker.yml is no longer silently ignored.

3. When a user provides a static object in YAML, it is passed through
   directly instead of being merged with the (now-removed) buggy base.

Also removes static.watch from the default shakapacker.yml template,
since static is now false by default and the watch config was only
needed to work around the old buggy static default.

Fixes #1031

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ihabadham ihabadham force-pushed the ihabadham/fix/static-config-bugs branch from 2645056 to e7a1417 Compare March 30, 2026 16:24
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 30, 2026

Review

This is a solid, focused fix for a real pain point. The root cause analysis is correct: the old static.publicPath was set to a filesystem path (outputPath) rather than a URL path, causing webpack-dev-server to default-watch public/, which is already owned by Rails' ActionDispatch::Static. Setting static: false is the right default for Shakapacker's proxy-based architecture.

What is good

  • The truthy-check fix (devServerYamlConfig.static!== undefined) correctly preserves an explicit static: false from YAML that was previously silently ignored.
  • Removing the stale static.watch.ignored template entry is clean housekeeping.
  • Tests are thorough and use the same module-mutation pattern as the rest of the test suite.
  • The outputPath/contentBase import is correctly removed along with its usage.

One issue to address

See the inline comment on lines 104–113 of webpackDevServerConfig.ts: static: true (and other valid webpack-dev-server values like strings/arrays) is silently dropped, leaving config.static at false. webpack-dev-server v4 accepts boolean | string | string[] | Static | Static[], so a user who explicitly opts back into static serving with static: true or static: '/custom' would find their setting quietly ignored. A comment explaining the intentional scope, or extending the guard to forward other truthy values, would prevent future confusion.

Minor nit

The test for "passes through static: false from YAML config" is technically redundant with the default test (the default is already false), though it is useful as explicit documentation of the YAML-passthrough contract. No action needed.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7a1417458

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 30, 2026

Review

The fix is correct and well-motivated. The old default (static: { publicPath: contentBase }) was misconfigured: contentBase (outputPath) is a filesystem path not a URL path, and the missing directory field caused webpack-dev-server to fall back to watching the project root. Setting static: false is the right call given Shakapacker's proxy architecture.

Bug: unhandled truthy non-object static value

When static is set to true (or any other truthy scalar) in shakapacker.yml, neither branch in the new if / else if block matches and config.static is silently left as false. webpack-dev-server v4/v5 treats static: true as "use defaults" (serve from project root), so a user who had this value would get the opposite behavior with no warning. This is a behavior regression from the old code, which at least preserved its default when static was truthy. An else branch that warns and defaults to false, or explicitly passes true through, would address this.

Minor: one test does not cover the logic it implies

The test "passes through static: false from YAML config" sets devServer.static = false and asserts config.static === false, but since the default is already false the test would pass even if the YAML override branch was completely absent. A more effective version would verify that the static key is consumed from devServerYamlConfig (not leaked into the camelCase mapping loop), or check that static: false in YAML wins over a non-false starting state.

Everything else looks good

  • Removing outputPath / contentBase from the import is correct cleanup.
  • Changing the truthy check to !== undefined correctly handles static: false from YAML — that is the core bug fix.
  • Tests for the object passthrough, devMiddleware.publicPath, hmr to hot mapping, and snake_case conversion are solid.
  • Removing static.watch from the install template is correct.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 30, 2026

Review

The fix is well-motivated and the core logic is sound. The three bugs (wrong default, static: false silently ignored, stale template artifact) are all correctly addressed, and the new tests give good coverage of the key paths.

Two issues to address before merging:

1. TypeScript type doesn't cover true (or other passthrough values)

The WebpackDevServerConfig["static"] type is false | { ... }, but the else branch passes through any non-false, non-object value as-is — including true, which is tested. The as WebpackDevServerConfig["static"] cast in that branch silently lies to the type checker. Widening the type to boolean | { ... } (or to match the full webpack-dev-server signature) would make the types honest. See inline comment.

2. Off-by-one indentation in the else closing brace

A stray space gives the closing } 5-space indentation instead of 4. See inline comment.

Everything else looks good:

  • Removing outputPath/contentBase from the static default is the right call — it was a filesystem path being used where a URL was expected.
  • The !== undefined guard correctly catches static: false where the old truthy check didn't.
  • The delete devServerYamlConfig.static placement (outside the inner if-else but inside the outer guard) is correct.
  • Test setup with jest.resetModules() + mutating the required module before loading the config under test is the right pattern for this module structure.

… them

If a user sets static: true, a string, or any other value in
shakapacker.yml, pass it through to webpack-dev-server as-is
instead of silently discarding it.
@ihabadham ihabadham force-pushed the ihabadham/fix/static-config-bugs branch from 3f975a9 to 2c4bedd Compare March 30, 2026 16:49
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2c4bedd784

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 30, 2026

Overall: solid fix for a real bug. The truthy-check correction and false default are correct. One type-safety issue to address: see inline comments.

- Type now accepts boolean | string | string[] | object (matching
  webpack-dev-server's actual schema) instead of just false | object
- Bare `static:` in YAML (parses as null via js-yaml) is now
  treated the same as undefined — skipped, default false sticks
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 30, 2026

Review

The fix is correct and well-motivated. Defaulting static: false prevents webpack-dev-server from inotify-watching public/, and the truthy-check bug (if (devServerYamlConfig.static)) silently eating static: false from YAML is a real regression worth fixing. The test coverage for the new behaviour is good.

A few nits/issues to address:


1. Redundant static === false branch (minor)

config.static is initialised to false on the line above, so the === false arm just re-assigns the same value. The only meaningful side-effect is the delete devServerYamlConfig.static at the bottom of the outer block, which runs regardless of which branch is taken. The branch can be removed entirely without changing behaviour.


2. Unnecessary !== null guard (minor)

YAML static: false is deserialised to the JS boolean false, never to null. The only way to get null here is static: ~ / bare static: in the YAML, which isn't a meaningful webpack-dev-server value. The check !== undefined alone is sufficient.


3. Array passthrough has an incorrect TypeScript cast (worth fixing)

webpack-dev-server accepts static as an array (Array<string | Static>) — the type comment on DevServerConfig.static already documents this. If a user configures:

static:
  - "/path1"
  - "/path2"

…then typeof [] === "object" is true, so the object branch is taken and the value is cast to Record<string, unknown>. The runtime value flowing through is fine, but the TypeScript cast is wrong. An Array.isArray guard would fix the type fidelity:

config.static = Array.isArray(devServerYamlConfig.static)
  ? (devServerYamlConfig.static as string[])
  : (devServerYamlConfig.static as Record<string, unknown>)

4. Missing test for string and array passthrough (minor)

There is no test exercising static: "/path" (string) or static: ["/p1", "/p2"] (array) — both valid webpack-dev-server values handled by the else/object branches. Worth adding at least the string case.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0083f34f51

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 30, 2026

Review: Fix webpack-dev-server static config

The approach is correct and the fix addresses real bugs. A few observations:

The core fix is sound

  • Defaulting static: false is the right call — Rails handles public/ via ActionDispatch::Static, so webpack-dev-server doesn't need to watch it
  • The truthy-check fix (!== undefined && !== null instead of if (devServerYamlConfig.static)) correctly allows false to pass through
  • Removing the static.watch.ignored default from the template is the right cleanup

Minor issues flagged inline

See inline comments for specifics on a redundant code branch and a test that doesn't actually verify the fix it describes.

@ihabadham ihabadham requested a review from justin808 March 30, 2026 20:13
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.

webpack-dev-server watches public/ unnecessarily due to static config bugs

1 participant