Skip to content

feat(rpc): expose node version + git commit on getNetworkInfo#864

Merged
tcsenpai merged 1 commit into
stabilisationfrom
feat/expose-node-version
May 26, 2026
Merged

feat(rpc): expose node version + git commit on getNetworkInfo#864
tcsenpai merged 1 commit into
stabilisationfrom
feat/expose-node-version

Conversation

@tcsenpai
Copy link
Copy Markdown
Contributor

Summary

Adds nodeVersion to the getNetworkInfo response so operators (and the SDK) can tell at a glance which binary a node is actually running.

The trigger was: PR #861 (GCREdit hash normalisation) landed on stabilisation, but dev.node2.demos.sh:53552 kept returning GCREdit mismatch on every post-fork transfer. The investigation forked into "is the fix wrong?" vs. "did the host get rebuilt?" — the latter would have been a one-second answer if the RPC had told us the running commit. Same diagnostic value for any future fix that gets merged and rolled out across multiple validator hosts.

Shape

NetworkInfo now carries a nodeVersion block:

{
  "forks": { "osDenomination": {  } },
  "nodeVersion": {
    "name": "demos-node-software",
    "version": "0.9.8",
    "commit": "4fff076da34456a99a9eac2a5caadf0b300476c8",
    "commitShort": "4fff076",
    "branch": "stabilisation",
    "dirty": false,
    "builtAt": null
  }
}

Additive — older SDKs that only destructure forks keep working.

Sources

src/utilities/nodeVersion.ts resolves at module evaluation (frozen for the binary's lifetime):

  • name + version — top-level package.json
  • commit + commitShort.git/HEAD walked up from cwd (handles ref-pointer HEADs, detached HEADs, packed-refs fallback)
  • branch — HEAD ref name when applicable
  • dirtygit diff-index --quiet HEAD, tolerant of missing git binary
  • builtAtBUILT_AT env when a Docker image baked one in, else null

Env overrides (GIT_COMMIT, GIT_BRANCH, GIT_DIRTY) take precedence when present so stripped images (git clone --depth 0, multistage builds that don't ship .git/) can still surface meaningful values from build args.

Defensive failure modes

Every individual failure path lands on null. A corrupted .git/HEAD, missing git binary, or unreadable refs cannot panic the node — getNetworkInfo MUST keep answering even when provenance is unknown.

Test plan

  • bun run type-check-ts — no new errors (pre-existing chainBlocks.ts duplicate-import + worker-threads-test errors unchanged)
  • Smoke: bun --eval import returned the expected current SHA, short SHA, branch, and dirty flag from the working tree.
  • No unit test added — module is pure-IO with environment dependencies tedious to mock and every failure mode returns null instead of throwing. A future PR can add a fixture-driven test if regressions appear.

Operator usage

curl -sS -X POST -H 'Content-Type: application/json' \
     -d '{"method":"nodeCall","params":[{"type":"nodeCall","message":"getNetworkInfo","data":{}}]}' \
     http://your-node-rpc/ | jq .response.nodeVersion

…) via getNetworkInfo

Motivation: when chasing a "fix-merged-to-stabilisation-but-the-symptom-
still-reproduces" bug, the first question is always "did this node get
rebuilt after the fix landed?". Without a deterministic answer the
investigation forks into "is the fix actually wrong?" vs. "is the
operator running stale bytes?". Surfacing the running binary's
provenance on the existing fork-status RPC closes that branch in one
round-trip.

`src/utilities/nodeVersion.ts` is a single source of truth that
resolves at module evaluation (frozen for the binary's lifetime):

  - `name` + `version`         from top-level `package.json`
  - `commit` + `commitShort`   from `.git/HEAD` walked up from cwd
                               (handles ref-pointer HEADs, detached
                               HEADs, and `packed-refs` fallback)
  - `branch`                   from the HEAD ref name when applicable
  - `dirty`                    via `git diff-index --quiet HEAD`,
                               tolerant of missing git binary
  - `builtAt`                  from BUILT_AT env if a Docker image
                               baked one in; otherwise null

Env-var overrides (`GIT_COMMIT`, `GIT_BRANCH`, `GIT_DIRTY`) take
precedence when present so a stripped image (`git clone --depth 0`,
multistage builds that don't ship `.git/`) can still surface
meaningful values from build args. Every lookup is defensive — every
individual failure path lands on `null`, never throws — so a
corrupted `.git/HEAD` or a runtime missing `child_process` cannot
panic the node. `getNetworkInfo` MUST keep answering even when
provenance is unknown.

`NetworkInfo.nodeVersion` is additive — older SDKs that only
destructure `forks` keep working. Operators get the info via the
same call they already poll for fork status:

  $ curl -sS -X POST -H 'Content-Type: application/json' \
       -d '{"method":"nodeCall","params":[{"type":"nodeCall",
            "message":"getNetworkInfo","data":{}}]}' http://node.../
    "nodeVersion": {
      "name": "demos-node-software",
      "version": "0.9.8",
      "commit": "4fff076da34456a99a9eac2a5caadf0b300476c8",
      "commitShort": "4fff076",
      "branch": "stabilisation",
      "dirty": false,
      "builtAt": null
    }

Test plan: smoke-tested locally via `bun --eval` import of the module
on the dev branch (returned the expected current SHA + branch). No
unit test added — module is pure-IO with environment dependencies that
are tedious to mock and the failure modes are all "return null", not
"throw". A future PR can add a fixture-driven test if regressions
appear.
@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 aec4d59 into stabilisation May 26, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

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 40 minutes and 22 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: 7e8e6074-8e2b-46dd-a0aa-923b0dd06f58

📥 Commits

Reviewing files that changed from the base of the PR and between 4fff076 and 9dd74ee.

📒 Files selected for processing (2)
  • src/libs/network/handlers/forkHandlers.ts
  • src/utilities/nodeVersion.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/expose-node-version

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 feat/expose-node-version branch May 26, 2026 14:39
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 26, 2026

Greptile Summary

This PR exposes build provenance (nodeVersion) on the existing getNetworkInfo RPC so operators can confirm which binary a node is actually running in one round-trip. The change is additive: forks consumers are unaffected.

  • src/utilities/nodeVersion.ts — new module that resolves name/version from package.json, commit/branch/dirty from .git/HEAD (with a packed-refs fallback and env-var override path), and builtAt from BUILT_AT. All paths are try/catch-wrapped so any single failure returns null rather than throwing.
  • src/libs/network/handlers/forkHandlers.tsNetworkInfo interface gains a nodeVersion: NodeVersionInfo field and the handler appends the frozen NODE_VERSION snapshot to every getNetworkInfo response.

Confidence Score: 3/5

Safe to merge after adding a timeout to the execFileSync call — without it, a hung git binary will stall the node process at startup with no recovery path.

The execFileSync('git', ['diff-index', ...]) call runs synchronously during module evaluation with no timeout option. A stale .git/index.lock, a credential helper waiting for input, or a slow NFS-backed .git/ directory can cause the node process to hang indefinitely at startup — silently, with no RPC answering. This is the exact failure mode the module was designed to avoid. The fix is a one-liner (timeout: 5000), but it should land before this ships to validators.

src/utilities/nodeVersion.ts — specifically the execFileSync call at line 188 that lacks a startup timeout.

Important Files Changed

Filename Overview
src/utilities/nodeVersion.ts New module that reads package.json, .git/HEAD, and runs git diff-index synchronously at module evaluation. The git exec call lacks a timeout, which can hang startup; all other failure paths are defensively handled.
src/libs/network/handlers/forkHandlers.ts Adds nodeVersion: NODE_VERSION to the getNetworkInfo response and extends the NetworkInfo interface accordingly. Change is additive and straightforward.

Sequence Diagram

sequenceDiagram
    participant Client
    participant forkHandlers
    participant NODE_VERSION
    participant readGitInfo
    participant fs as .git/HEAD
    participant git as git binary

    Note over NODE_VERSION,git: Module evaluation (once at startup)
    NODE_VERSION->>readGitInfo: readGitInfo()
    readGitInfo->>fs: readFileSync(.git/HEAD)
    fs-->>readGitInfo: ref or SHA
    readGitInfo->>git: execFileSync(diff-index --quiet HEAD)
    git-->>readGitInfo: exit 0 (clean) / exit 1 (dirty)
    readGitInfo-->>NODE_VERSION: "{commit, branch, dirty}"
    Note over NODE_VERSION: frozen snapshot

    Client->>forkHandlers: getNetworkInfo RPC
    forkHandlers->>NODE_VERSION: NODE_VERSION (read frozen value)
    NODE_VERSION-->>forkHandlers: NodeVersionInfo
    forkHandlers-->>Client: "{forks: {...}, nodeVersion: {...}}"
Loading

Reviews (1): Last reviewed commit: "feat(rpc): surface node identity (name +..." | Re-trigger Greptile

Comment on lines +187 to +198
try {
execFileSync("git", ["diff-index", "--quiet", "HEAD"], {
cwd: repoRoot,
stdio: "ignore",
})
dirty = false
} catch (e) {
const code = (e as { status?: number }).status
// Exit 1 = dirty. Anything else (binary missing, unreadable
// refs) = "we don't know"; report clean rather than panic.
dirty = code === 1
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 execFileSync without a timeout blocks node startup indefinitely

execFileSync is called synchronously during module evaluation with no timeout option. If git is installed but hangs — stale lock file at .git/index.lock, a credential helper prompting for input, or an NFS-backed .git/ with a dropped connection — the entire node process hangs at startup with no way to recover. The comment describes tolerating a missing binary, but a hung binary is not covered. Adding timeout: 5000 (or similar) ensures the call fails fast when git is unresponsive.

Suggested change
try {
execFileSync("git", ["diff-index", "--quiet", "HEAD"], {
cwd: repoRoot,
stdio: "ignore",
})
dirty = false
} catch (e) {
const code = (e as { status?: number }).status
// Exit 1 = dirty. Anything else (binary missing, unreadable
// refs) = "we don't know"; report clean rather than panic.
dirty = code === 1
}
execFileSync("git", ["diff-index", "--quiet", "HEAD"], {
cwd: repoRoot,
stdio: "ignore",
timeout: 5000,
})

Comment on lines +50 to +51
/** Full 40-char git SHA, or `null` if not resolvable. */
commit: string | null
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 GIT_COMMIT env var accepts 7–40 character strings (the regex lower bound is 7), but NodeVersionInfo.commit is documented as "Full 40-char git SHA." When GIT_COMMIT carries a short SHA, commitShort becomes identical to commit, and any downstream consumer relying on commit being the full SHA will get a truncated value without warning. The JSDoc should reflect the actual range.

Suggested change
/** Full 40-char git SHA, or `null` if not resolvable. */
commit: string | null
/** Full git SHA, or `null` if not resolvable. When sourced from the `GIT_COMMIT` env var, may be a short SHA (7–40 chars). */
commit: string | null

tcsenpai added a commit that referenced this pull request May 26, 2026
… require() (#865)

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.

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