Skip to content

fix(config): report why the config module failed, not only which file - #3657

Merged
kojiwakayama merged 2 commits into
mainfrom
fix/config-load-error-names-cause
Aug 13, 2026
Merged

fix(config): report why the config module failed, not only which file#3657
kojiwakayama merged 2 commits into
mainfrom
fix/config-load-error-names-cause

Conversation

@kojiwakayama

@kojiwakayama kojiwakayama commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Second defect found while following #3597's CSS-optimizer hint literally in a scratch project built from published veryfront@0.1.1232. (The hint itself is fixed in a separate PR.)

Reproduction

A reader told to put the extension in veryfront.config.ts writes the natural first guess:

import { defineConfig } from "veryfront/config";
import extCSSLightning from "@veryfront/ext-css-lightning";
export default defineConfig({ extensions: [extCSSLightning()] });

veryfront exports no ./config subpath — defineConfig is on the package root — so Node raises ERR_PACKAGE_PATH_NOT_EXPORTED with a message that says exactly what is wrong:

Package subpath './config' is not defined by "exports" in .../veryfront/package.json

The loader drops it:

  ! Failed to load config file
                       configFile=veryfront.config.ts
  ✗ Failed to load veryfront.config.ts
✗ [config-parse-error] Failed to parse configuration
  Detail: Failed to load veryfront.config.ts
  Suggestion: Ensure your configuration file contains valid JavaScript or TypeScript

The file's syntax was never the problem, so the suggestion sends the reader looking in the wrong place. cause is attached to the error, but nothing between the loader and the terminal reads it — including at LOG_LEVEL=debug, which adds only loadAndMergeConfig called and then the same bare line.

Change

The three Failed to load <file> sites in src/config/loader.ts that wrap evaluation of the project's config module append a summary of the cause. The summary is redacted with sanitizeUrlCredentials, then reduced to one line, bounded to 200 characters, and stripped of control characters: the cause is authored by the project being loaded, and a hosted build log must not become a paste surface for it. Redaction runs before the bound because sanitizeUrlCredentials recognizes userinfo by its trailing @host, and a cut that drops the @host would leave a password prefix behind -- the ordering sanitizeBoundedDiagnosticText already documents at src/errors/diagnostic-policy.ts:63-72.

The fourth site, in readHostedConfigSource, keeps the generic detail. Everything reaching that catch came out of adapter.fs.readFile, so the cause describes the storage backend rather than the project's config module, and CONFIG_PARSE_ERROR is a 400 whose detail the HTTP boundary does not strip (src/errors/middleware/http-error-boundary.ts:113-116). The cause stays attached for the logs.

Tests

Red first, three new cases in src/config/loader.test.ts:

error: AssertionError: error must name the subpath that failed to resolve,
       got: Failed to load veryfront.config.js
error: AssertionError: error must repeat what the config threw,
       got: Failed to load veryfront.config.js
error: AssertionError: error must keep the first line,
       got: Failed to load veryfront.config.js

The third pins the bound: a cause with "first line\nsecond line " + "A".repeat(4096) must keep the first line, drop the rest, and leave the report under 512 characters.

A fourth case, added after review, pins the redaction order rather than the presence of a call. Its padding is sized so https://svc: ends at character 190 and the @ sits at 209, putting the marker the redactor matches on past the bound. With sanitizeUrlCredentials moved back after the bound it fails:

error: AssertionError: error must not carry a prefix of the password, got:
  Failed to load veryfront.config.js: upstream refused BBBB...BBBB https://svc:sup3rsecr...

The unresolvable specifier in the test is veryfront/not-an-export, not the veryfront/config from the field report, because this repository's own deno.json maps veryfront/config to src/config/index.ts for internal callers, so it resolves inside the test process. That asymmetry — a subpath that is real in the monorepo and absent from the published exports map — is why the guess is natural in the first place, and it is called out in the test comment.

End-to-end verification

The published 0.1.1232 npm package with only esm/src/config/ replaced by this branch's deno task build:npm output, same scratch project, same wrong import:

  Detail: Failed to load veryfront.config.ts: Package subpath './config' is not
          defined by "exports" in /private/tmp/…

Not changed

The config-parse-error suggestion still reads "Ensure your configuration file contains valid JavaScript or TypeScript", which remains wrong for a resolution failure. Narrowing the suggestion per cause class is a larger change to the error registry and is left out of this fix.

🤖 Generated with Claude Code

A reader following the CSS-optimizer hint into a `veryfront.config.ts` writes
the natural first guess:

  import { defineConfig } from "veryfront/config";

`veryfront` exports no `./config` subpath -- `defineConfig` is on the package
root -- so Node raises ERR_PACKAGE_PATH_NOT_EXPORTED with a message that says
exactly what is wrong: "Package subpath './config' is not defined by "exports"
in .../veryfront/package.json". Verified against published 0.1.1232, the loader
drops that message and prints:

  ! Failed to load config file   configFile=veryfront.config.ts
  ✗ [config-parse-error] Failed to parse configuration
    Detail: Failed to load veryfront.config.ts
    Suggestion: Ensure your configuration file contains valid JavaScript or
                TypeScript

The file's syntax was never the problem, so the suggestion sends the reader
looking in the wrong place, and nothing recovers the discarded reason -- `cause`
is attached to the error but nothing between the loader and the terminal reads
it, at any log level, including LOG_LEVEL=debug.

So the four `Failed to load <file>` sites now append a summary of the cause.
It is one line, bounded to 200 characters, and stripped of control characters:
the cause is authored by the project being loaded, and a hosted build log must
not become a paste surface for it.

The unresolvable specifier in the test is `veryfront/not-an-export`, not the
`veryfront/config` from the field report, because this repository's own
deno.json maps `veryfront/config` to src/config/index.ts for internal callers,
so it resolves inside the test process. That asymmetry -- a subpath that is real
in the monorepo and absent from the published exports map -- is why the guess is
natural in the first place.
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@kojiwakayama, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: aa72ff25-402a-407c-8e9f-714910bc5a8e

📥 Commits

Reviewing files that changed from the base of the PR and between 688cb7f and 5523179.

📒 Files selected for processing (2)
  • src/config/loader.test.ts
  • src/config/loader.ts

Comment @coderabbitai help to get the list of available commands.

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

Copy link
Copy Markdown

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: 2fd5b154bf

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/config/loader.ts Outdated
Comment thread src/config/loader.ts
… generic

Two review findings on the cause-reporting change.

The summary truncated before redacting. `sanitizeUrlCredentials` recognizes
userinfo by the trailing `@host`, so cutting to 200 characters first can drop
that marker and leave a password prefix in a CONFIG_PARSE_ERROR detail -- and
the HTTP boundary strips `detail` only at 5xx, so a 400 carries it to the
caller. Redaction now runs over the complete message, the order
`sanitizeBoundedDiagnosticText` already documents for the same reason.

`readHostedConfigSource` goes back to the generic detail. Everything reaching
that catch came out of `adapter.fs.readFile`, so the cause describes the storage
backend, not the project's config module. It stays on `cause` for the logs.

The new test pins the ordering rather than the presence: padding places the `@`
past the bound, so an unredacted cut keeps nine characters of the secret. It
fails with redaction moved after the bound.

Also casts the three new `assertRejects` results to `VeryfrontError`, matching
the rest of the file -- without it `lint:test-typecheck` reported twelve TS18046
errors and its recursive split surfaced five unrelated files as collateral.
@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 13, 2026
Merged via the queue into main with commit 510bcfb Aug 13, 2026
33 checks passed
@kojiwakayama
kojiwakayama deleted the fix/config-load-error-names-cause branch August 13, 2026 07:19
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