Skip to content

fix(rpc): readPackageJson via fs walk so getNetworkInfo.nodeVersion isn't sentinel defaults in production - #865

Merged
tcsenpai merged 1 commit into
stabilisationfrom
fix/node-version-package-resolve
May 26, 2026
Merged

fix(rpc): readPackageJson via fs walk so getNetworkInfo.nodeVersion isn't sentinel defaults in production#865
tcsenpai merged 1 commit into
stabilisationfrom
fix/node-version-package-resolve

Conversation

@tcsenpai

Copy link
Copy Markdown
Contributor

Repro

After PR #864 merged + dev.node2 rebuilt, getNetworkInfo.nodeVersion came back as:

"nodeVersion": {
  "name": "demos-node",
  "version": "0.0.0",
  "commit": null, "commitShort": null,
  "branch": null, "dirty": false, "builtAt": null
}

Those are exactly the catch-clause defaults. require(\"../../package.json\") was silently failing under bun + tsconfig-paths' ESM/CJS interop, and the failure landed on the wrong-answer fallback instead of throwing loudly.

Fix

Walk from import.meta.url up to the first directory containing a readable package.json — same pattern the .git/HEAD walker already uses. Skips nested workspace manifests by requiring a non-empty version field. Hard cap at 16 levels so a corrupted fs cannot loop. Defensive fileURLToPath fallback to cwd preserves the "every failure path returns null" invariant.

.git/ absence in the image is expected and stays null — that part of the response already does what it should.

Test plan

  • bun --eval smoke locally returns real values:
    { name: "demos-node-software", version: "0.9.8",
      commit: "aec4d59…", commitShort: "aec4d59",
      branch: "stabilisation", dirty: true, builtAt: null }
    
  • bun run type-check-ts — no new errors
  • Verify in production by rebuilding dev.node2 once merged + curl'ing getNetworkInfo again; expect non-sentinel name/version (commit/branch likely still null until .git/ is shipped or the GIT_COMMIT env-var override is set)

Followup (optional)

If we want commit populated in production too, ship the SHA via a Dockerfile build arg:

ARG GIT_COMMIT
ENV GIT_COMMIT=$GIT_COMMIT

Then docker compose build --build-arg GIT_COMMIT=$(git rev-parse HEAD). Out of scope for this PR.

… require()

The require("../../package.json") path used in PR #864 silently failed
in production (the rebuilt dev.node2 returned name: "demos-node" +
version: "0.0.0" — the catch-clause defaults). bun + tsconfig-paths
under ESM has unreliable behaviour around `require()`-resolving JSON,
and the failure mode was a silent catch falling through to the wrong
answer rather than a loud throw.

Walk from `import.meta.url` up to the first directory containing a
readable package.json — same pattern the .git/HEAD walker below already
uses, so both halves of the module now behave identically. Skips
nested workspace manifests by requiring a non-empty version field.

Hard cap at 16 levels so a corrupted fs cannot loop. Defensive
fileURLToPath fallback to cwd preserves the "every failure path
returns null" invariant.

After this fix, dev.node2's getNetworkInfo.nodeVersion surfaces the
real package name + semver + git provenance instead of sentinel
defaults, restoring the diagnostic value the original PR intended.
@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@tcsenpai
tcsenpai merged commit 0c228ab into stabilisation May 26, 2026
1 check passed
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@tcsenpai, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 32 minutes and 32 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

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.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: befda6f2-b608-4c62-bc35-2a73433ea42f

📥 Commits

Reviewing files that changed from the base of the PR and between aec4d59 and 128b78c.

📒 Files selected for processing (1)
  • src/utilities/nodeVersion.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/node-version-package-resolve

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.

@tcsenpai
tcsenpai deleted the fix/node-version-package-resolve branch May 26, 2026 14:47
@greptile-apps

greptile-apps Bot commented May 26, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes getNetworkInfo.nodeVersion returning sentinel defaults in production by replacing a brittle require("../../package.json") call — which was silently failing under bun + tsconfig-paths ESM/CJS interop — with a filesystem walker that climbs from import.meta.url up to the first package.json containing a non-empty version field, mirroring the existing .git/HEAD walker pattern.

  • readPackageJson rewritten: uses import.meta.url as the anchor (falling back to process.cwd() on error), walks up at most 16 directory levels, skips manifests with no version field, and returns the same sentinel defaults only if no valid manifest is found.
  • No other logic changed: git-info resolution, env-var overrides, and the frozen module-level snapshot are untouched.

Confidence Score: 4/5

Safe to merge; the core fix is correct and well-bounded, with only minor comment inaccuracies.

The upward fs-walk correctly finds the root package.json in both the dev tree and the Docker container layout, and the 16-level hard cap prevents any runaway on a corrupted fs. The two comments are documentation issues only and do not affect runtime behavior.

Only src/utilities/nodeVersion.ts changed; no other files need attention.

Important Files Changed

Filename Overview
src/utilities/nodeVersion.ts Replaces brittle require("../../package.json") with an upward fs-walk from import.meta.url; fix is correct for the reported production regression, with two minor documentation inaccuracies in the new comments

Reviews (1): Last reviewed commit: "fix(rpc): readPackageJson via fs walk fr..." | Re-trigger Greptile

Comment on lines +74 to +77
* sentinels). Walking from `import.meta.url` up to the first
* directory that contains a readable `package.json` mirrors what
* the git-root walker below already does for `.git/HEAD`, so the
* two halves of the module behave identically.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Inaccurate symmetry claim in the JSDoc — findRepoRoot still starts from process.cwd() (line 166), while the new readPackageJson starts from import.meta.url. The two walkers therefore have different anchors in the normal (non-fallback) path. The claim that they "behave identically" only holds when import.meta.url throws and the fallback process.cwd() kicks in. No correctness impact for the stated use case (the upward walk from the module dir is strictly better for finding the root package.json), but the comment will confuse future readers.

Comment on lines +96 to +99
// Skip nested package manifests (e.g. node_modules/*) by
// requiring a non-empty `version` field — those exist on
// every workspace's package.json, including ours.
if (typeof pkg.version === "string" && pkg.version.length > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 The comment overstates what the version-presence check actually filters. Packages in node_modules virtually always have a non-empty version field, so the check does not suppress them. What it genuinely skips is private workspace-root manifests that omit version entirely. The upward-walk direction is what keeps node_modules entries out of the picture — they live in child directories, never in parent directories relative to the module file.

Suggested change
// Skip nested package manifests (e.g. node_modules/*) by
// requiring a non-empty `version` field — those exist on
// every workspace's package.json, including ours.
if (typeof pkg.version === "string" && pkg.version.length > 0) {
// Skip private workspace roots that omit `version` entirely.
// Actual node_modules entries are never encountered because the
// walk moves upward through parent directories, not downward.
if (typeof pkg.version === "string" && pkg.version.length > 0) {

tcsenpai added a commit that referenced this pull request May 26, 2026
…from docker-run (#866)

Closes the gap left by PR #864/#865: src/utilities/nodeVersion.ts
already reads process.env.GIT_COMMIT/GIT_BRANCH/GIT_DIRTY when present,
but nothing on the docker path was supplying them. The result was that
getNetworkInfo.nodeVersion came back with commit: null on every Docker-
booted node, defeating the original "is this host running the binary I
think it is?" diagnostic value.

Three pieces wire it end-to-end:

1) Dockerfile (runtime stage): ARG GIT_COMMIT/GIT_BRANCH/GIT_DIRTY/
   BUILT_AT, then re-export each via ENV so the running node sees them
   in process.env. Defaults are empty strings — a build without the
   args (manual `docker build .`) still produces a runnable image,
   nodeVersion just surfaces null for the missing fields.

2) docker-compose.yml (node service build block): args passes the
   four values straight through with `:-` defaults, so any caller
   that exported them (the wrapper below, CI, an operator's shell)
   gets them baked in; anyone who didn't gets an empty-string ARG
   and the same null-field fallback.

3) scripts/docker-run: detects the working tree's git status with
   defensive guards (no-repo, no-git-binary, dirty-vs-clean) and
   exports the four variables before any `docker compose build`/`up`.
   `git diff-index --quiet HEAD` exit code → "true"/"false" string the
   nodeVersion module already parses. BUILT_AT is the UTC ISO-8601
   timestamp at invocation.

Operator-visible effect: after `./scripts/docker-run --rebuild -d`,
curl getNetworkInfo and the response carries the real commit SHA,
branch name, dirty flag, and build timestamp the image was made from.
That answers "did this host actually pick up the fix I merged?" with
a one-liner.

Manual repro paths still work:
  docker compose build --build-arg GIT_COMMIT=$(git rev-parse HEAD)
  GIT_COMMIT=abc123 docker compose build

Co-authored-by: tcsenpai <tcsenpai@discus.sh>
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