Skip to content

feat(web): enrich /api/health with process info (version, uptime, startedAt, node, pid)#1514

Open
Harsh23Kashyap wants to merge 7 commits into
sourcebot-dev:mainfrom
Harsh23Kashyap:feat/health-process-info
Open

feat(web): enrich /api/health with process info (version, uptime, startedAt, node, pid)#1514
Harsh23Kashyap wants to merge 7 commits into
sourcebot-dev:mainfrom
Harsh23Kashyap:feat/health-process-info

Conversation

@Harsh23Kashyap

@Harsh23Kashyap Harsh23Kashyap commented Jul 27, 2026

Copy link
Copy Markdown

Summary

Enriches GET /api/health (the public liveness probe) with the standard process facts operators need: Sourcebot version, process start timestamp, uptime in seconds, Node.js version + platform + arch, and the process PID. The existing status: "ok" field is preserved so Kubernetes livenessProbe configs that only check the HTTP code keep working unchanged.

Fixes #1513.

Motivation

The public liveness endpoint currently returns just { "status": "ok" }. Self-hosted operators who wire Sourcebot into a livenessProbe, or who curl the endpoint from fleet-management scripts, have no way to:

  • Verify the build they deployed is the one actually running (a one-shot curl /api/health is enough — no need to also call /api/version).
  • Detect a restart (a sudden drop in uptime is an unambiguous signal that a K8s node reboot took the pod with it).
  • Correlate logs with the running version (support tickets can paste a single curl response).

The complementary readiness probe (GET /api/health/ready) already covers dependency health, but the liveness endpoint is the right place for "is this process up and what is it?". Splitting the concerns keeps the two endpoints single-purpose.

Changes

  • packages/web/src/app/api/(server)/health/route.ts — captures a frozen startedAt ISO 8601 timestamp at module load and adds version, uptime, pid, and a node { version, platform, arch } block to the response. The handler is wrapped in apiHandler({ track: false }) and continues to bypass withAuth (the existing // eslint-disable-next-line authz/require-auth-wrapper comment is preserved).
  • packages/web/src/app/api/(server)/health/route.test.ts — new file. Seven vitest cases covering: status preservation, version matches the mocked SOURCEBOT_VERSION, startedAt is a parseable ISO 8601 in the past and recent, uptime is a non-negative integer, uptime increases between two requests (1.1s sleep), node matches process.version/process.platform/process.arch, pid matches process.pid.
  • packages/web/src/openapi/publicApiSchemas.tspublicHealthResponseSchema extended with the new fields. Backward-compatible additive change: existing consumers that only read status keep working.
  • docs/api-reference/sourcebot-public.openapi.json — regenerated via yarn workspace @sourcebot/web openapi:generate to match the schema.
  • CHANGELOG.md[Unreleased] → Added entry noting the new fields and the backward-compatibility guarantee.

Design decisions

  • Module-load startedAt, not per-request new Date(). Two reasons: (1) the field is genuinely the process start time, so per-request computation would be wrong; (2) caching it costs one Date.now() call at module load and zero per-request. The uptime field is the only per-request computation, and it's a single subtract-and-floor.
  • uptime is an integer in seconds, not a float. Operators wire this into Prometheus / Datadog / etc. via scrape configs and integer seconds is what every existing tool expects. Sub-second resolution is irrelevant for liveness.
  • Add a node object, not flat nodeVersion / nodePlatform / nodeArch fields. Mirrors the standard Node.js process facts as a single block, so the response shape is grep-able for body.node.version regardless of how many more node-level fields are added later.
  • The pid is at the top level, not under node. The PID is process-scoped, not Node-scoped, and matches what ps and similar tools expose.
  • No new env var, no build-time injection. The SOURCEBOT_VERSION constant is already in @sourcebot/shared. Everything else comes from process.*. A future PR could add a SOURCEBOT_COMMIT_SHA env var injected at build time via next.config.js, but that's a separate change.
  • Backward compatible. The status: "ok" field is preserved at the top level. Existing OpenAPI consumers that only read status keep working. Existing K8s livenessProbe configs that only check the HTTP code keep working.

Verification

  • yarn workspace @sourcebot/web test --run "health/route" — 7/7 tests pass.
  • yarn workspace @sourcebot/web test --run "health" — 7/7 tests pass (the readiness-probe test lives on PR feat(web): add /api/health/ready endpoint with dependency health checks #1507's branch and isn't on this one).
  • yarn workspace @sourcebot/web lint — 0 errors (4 pre-existing warnings in unrelated files).
  • tsc --noEmit -p packages/web/tsconfig.json — 0 errors in the new files.
  • yarn workspace @sourcebot/web openapi:generate — clean regen, the PublicHealthResponse component in the published doc now includes the new required properties.

The 7 unrelated test failures in yarn workspace @sourcebot/web test --run (in the EE chat/skills, chat/mcp, and askmcp/* areas) are pre-existing — they fail with TypeError: (0, core_1.getEnv) is not a function from @opentelemetry/sdk-trace-base, which is a setup issue independent of this PR. They are not in any file I changed.

Backward compatibility

Fully backwards compatible. The existing status: "ok" field is preserved. Consumers that only check the HTTP code (e.g. K8s livenessProbe.httpGet with no expectedStatusCodes) are unaffected. The new fields are additive.

Out of scope (potential follow-ups, not in this PR)

  • A SOURCEBOT_COMMIT_SHA env var injected at build time via next.config.js and exposed as body.commit so operators can match a running instance against a deployed image.
  • A ?strict=true follow-on to /api/health analogous to the readiness-probe mode (a hard-coded "always ok" probe has no equivalent, so the analogue would be a "include additional diagnostics" toggle — TBD whether that's worth a feature flag).
  • A /api/diagnostics endpoint that returns more detailed runtime state (memory, event-loop lag, etc.) for operators who want to dig deeper.

Risks

  • startedAt is captured at module load. In a serverless / edge deployment the module might be re-loaded per request and startedAt would reflect only the most recent init. Sourcebot is not deployed that way today (it's a long-running Node process), so this is theoretical.
  • The node.version exposes the runtime version, which is already visible via any unauthenticated error response. No new information leak.
  • The pid is the Node process PID. Same visibility story as node.version.

Note

Low Risk
Additive change to an already-public liveness endpoint; no auth or data-model changes. Slightly more runtime metadata (pid, Node version) is exposed, which matches typical health probes.

Overview
GET /api/health still returns status: "ok" for existing liveness checks, but the JSON body now includes operator-oriented process facts: Sourcebot version, startedAt (ISO 8601, derived once from process boot via process.uptime()), per-request uptime in whole seconds, pid, and a node block (version, platform, arch).

The public Zod/OpenAPI PublicHealthResponse schema and regenerated sourcebot-public.openapi.json document the new required fields. Vitest coverage was added for the response shape, timing consistency, and Node facts. CHANGELOG records the additive API change.

Reviewed by Cursor Bugbot for commit 286fbbd. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features
    • Expanded the health check response to include Sourcebot version, startup timestamp, uptime, process ID, and runtime details (platform/arch), while keeping the existing healthy status.
    • Updated the public OpenAPI schema to document the enhanced health response shape.
  • Improvements
    • Adjusted vulnerability triage to keep Linear issues synchronized with current security findings.
  • Tests
    • Added automated tests for the /api/health response fields and timing-related expectations.

…rtedAt, node, pid)

The public liveness probe currently returns a hardcoded { status: "ok" }
with no version, no uptime, and no process facts. Self-hosted operators
who wire Sourcebot into a Kubernetes livenessProbe — or who curl the
endpoint from fleet-management scripts — have no way to verify the
build they deployed is the one actually running, or to detect a restart.

Capture a frozen startedAt at module load (so two requests return the
same value and uptime increases monotonically) and add the standard
process facts: SOURCEBOT_VERSION, startedAt (ISO 8601), uptime
(seconds, floored), pid, and the Node { version, platform, arch } block.

The existing status:"ok" field is preserved so K8s livenessProbe
configs that only check the HTTP code keep working unchanged, and
older scripts that parse { status:"ok" } keep working. The endpoint
stays unauthenticated, track:false for PostHog, and continues to
bypass withAuth (the existing eslint-disable line for the
authz/require-auth-wrapper custom rule is preserved).

Closes sourcebot-dev#1513.
Seven cases, all on the same NextRequest stand-in:
- shape: 200, status:"ok" preserved
- version: matches the mocked SOURCEBOT_VERSION
- startedAt: parseable ISO 8601, in the past, recent (sanity-bounds to
  the last hour so a 1970 epoch would fail)
- uptime: non-negative integer
- uptime increases between two requests (1.1s sleep so the floored
  tick is observable on slow runners)
- node: matches process.version / process.platform / process.arch
- pid: matches process.pid

The mocks follow the same pattern as the readiness-probe test:
vi.mock('server-only', ...), vi.mock('@sourcebot/shared', ...) with a
fake SOURCEBOT_VERSION + a no-op createLogger, vi.mock('@/lib/posthog',
...) to keep the apiHandler wrapper's PostHog call from pulling in
the Sentry/OTel CJS chain at test-load time.
The public OpenAPI doc is auto-generated from the Zod schemas in
`packages/web/src/openapi/publicApiSchemas.ts`. To make the published
spec reflect the new /api/health response shape, extend
`publicHealthResponseSchema` with the additional fields and rerun
`yarn workspace @sourcebot/web openapi:generate`.

Schema additions mirror the route:
- version: z.string()
- startedAt: z.string().datetime() (ISO 8601)
- uptime: z.number().int().nonnegative() (seconds, floored)
- pid: z.number().int().positive()
- node: { version, platform, arch } all z.string()

This is purely additive: existing consumers that only consume
`status` keep working. The OpenAPI doc is regenerated and the
`PublicHealthResponse` component now includes the new required
properties.
Adds an [Unreleased] -> Added entry noting that /api/health now
returns the Sourcebot version, process start timestamp, uptime in
seconds, Node process facts (pid, version, platform, arch) and the
timestamp the process started. Notes that the existing status:"ok"
field is preserved for backward compatibility, and points at the
updated PublicHealthResponse in the public OpenAPI doc.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The /api/health endpoint now returns Sourcebot version, process start time, uptime, PID, and Node runtime facts. Zod and OpenAPI contracts, tests, and changelog entries were updated accordingly.

Changes

Health response enrichment

Layer / File(s) Summary
Health contract, implementation, and validation
packages/web/src/app/api/(server)/health/route.ts, packages/web/src/openapi/publicApiSchemas.ts, docs/api-reference/sourcebot-public.openapi.json, packages/web/src/app/api/(server)/health/route.test.ts
GET /api/health now returns version, start time, uptime, PID, and Node runtime facts. Zod/OpenAPI schemas and tests cover the expanded response while preserving status: "ok".
Unreleased changelog entries
CHANGELOG.md
Documents the expanded health response and vulnerability-triage synchronization with Linear issues.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HealthProbe
  participant HealthRoute
  participant NodeProcess
  HealthProbe->>HealthRoute: GET /api/health
  HealthRoute->>NodeProcess: Read version, uptime, pid, and runtime facts
  NodeProcess-->>HealthRoute: Process metadata
  HealthRoute-->>HealthProbe: Return expanded health response
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The changelog also adds an unrelated note about vulnerability triage/Linear sync, which is outside the /api/health enrichment scope. Split or remove that unrelated changelog entry into a separate PR.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The health route now returns the required version, uptime, startedAt, pid, and node details while preserving status: ok and public access.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: enriching /api/health with process/runtime information.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 11: Update the changelog entry describing the GET /api/health changes to
use a single sentence, preserving all listed details, and append the required PR
link in the format [#<id>](https://github.com/sourcebot-dev/sourcebot/pull/<id>)
with the actual pull request ID.

In `@packages/web/src/app/api/`(server)/health/route.ts:
- Around line 13-14: Replace the Date.now()-based uptime baseline in the health
route with a process-monotonic baseline, such as performance.now(), and compute
elapsed uptime from that baseline at the response site around the existing
uptime calculation. Preserve startedAt as the wall-clock ISO timestamp while
ensuring reported uptime remains non-negative and increasing.
🪄 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 Plus

Run ID: b1eeb430-93c0-4867-a080-66dd4ca0ded3

📥 Commits

Reviewing files that changed from the base of the PR and between 9491a13 and 6a342bc.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • packages/web/src/app/api/(server)/health/route.test.ts
  • packages/web/src/app/api/(server)/health/route.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment thread CHANGELOG.md Outdated
Comment thread packages/web/src/app/api/(server)/health/route.ts Outdated
Comment thread packages/web/src/app/api/(server)/health/route.ts Outdated
Address CodeRabbit + Bugbot findings on PR sourcebot-dev#1514: `startedAt` and
`uptime` were anchored to `Date.now()` at module load, so the
reported values lagged the actual process start by however long
Next.js took to first-load the route module (often minutes in a
cold-start scenario). Wall-clock math also shrinks or goes negative
after NTP steps.

Anchor both fields on `process.uptime()`, which is monotonic and
relative to process boot:

- `processStartedAtMs = Date.now() - Math.floor(process.uptime() * 1000)`
  captured once at module load: gives the wall-clock time the process
  started, accurate within milliseconds of the process start.
- `startedAt = new Date(processStartedAtMs).toISOString()` is the
  ISO 8601 timestamp of process boot, not module load.
- `uptime = Math.floor(process.uptime())` is the live process uptime,
  always monotonic, immune to NTP steps.

Tests:
- existing `uptime is non-negative integer` and `uptime increases
  between two requests` are unchanged.
- new `uptime is anchored to process boot, not module load`
  asserts the reported `uptime` matches the live `process.uptime()`
  within a 1s floor.
- new `startedAt is consistent with the process-start estimate`
  asserts `Date.parse(startedAt) + uptime * 1000 ≈ Date.now()` within
  1500ms, which is the round-trip consistency check.

The 1500ms tolerance accounts for two `Math.floor` roundings (one
in the captured start time, one in the per-request uptime) plus a
buffer for the request latency.
Address CodeRabbit finding on PR sourcebot-dev#1514: per the project changelog
convention, each entry should be a single sentence and the suffix
should link to the PR, not the issue.
Comment thread packages/web/src/app/api/(server)/health/route.test.ts
The recency assertion compared startedAt against now - 1 hour, but
startedAt is derived from process boot via process.uptime(), not
module-load wall time. On a vitest worker (or any Node process) older
than one hour, the assertion failed even though the handler was
correct. Widened the sanity bound to 30 days so a long-lived CI worker
does not flake while a literal "1970" start time still fails.
@Harsh23Kashyap

Copy link
Copy Markdown
Author

Opened #1514 to enrich /api/health with process info (version, uptime, startedAt, node, pid). The startedAt is derived from process boot via process.uptime() rather than module-load wall time, so the test's recency bound was widened to 30 days to avoid flaking on long-lived CI workers (the strict consistency with the process-uptime clock is enforced by a separate test). The cursor thread is resolved.

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 286fbbd. Configure here.

arch: process.arch,
},
};
return Response.json(body);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Health route missing force-dynamic

Medium Severity

GET /api/health now returns live process fields (uptime, pid, startedAt, SOURCEBOT_VERSION) but never sets export const dynamic = "force-dynamic". The sibling /api/version route documents that param-less GET handlers can be cached at build time and already opts out for that reason. Unit tests call GET directly, so they cannot catch a frozen cached response; a stuck uptime would defeat restart detection for operators.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 286fbbd. Configure here.

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.

[FR] Enrich /api/health with process info (version, uptime, startedAt, node)

1 participant