feat(web): enrich /api/health with process info (version, uptime, startedAt, node, pid)#1514
feat(web): enrich /api/health with process info (version, uptime, startedAt, node, pid)#1514Harsh23Kashyap wants to merge 7 commits into
Conversation
…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.
WalkthroughThe ChangesHealth response enrichment
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
CHANGELOG.mddocs/api-reference/sourcebot-public.openapi.jsonpackages/web/src/app/api/(server)/health/route.test.tspackages/web/src/app/api/(server)/health/route.tspackages/web/src/openapi/publicApiSchemas.ts
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.
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.
|
Opened #1514 to enrich |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ 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); |
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit 286fbbd. Configure here.


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 existingstatus: "ok"field is preserved so KuberneteslivenessProbeconfigs 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 alivenessProbe, or whocurlthe endpoint from fleet-management scripts, have no way to:curl /api/healthis enough — no need to also call/api/version).uptimeis an unambiguous signal that a K8s node reboot took the pod with it).curlresponse).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 frozenstartedAtISO 8601 timestamp at module load and addsversion,uptime,pid, and anode { version, platform, arch }block to the response. The handler is wrapped inapiHandler({ track: false })and continues to bypasswithAuth(the existing// eslint-disable-next-line authz/require-auth-wrappercomment is preserved).packages/web/src/app/api/(server)/health/route.test.ts— new file. Seven vitest cases covering: status preservation, version matches the mockedSOURCEBOT_VERSION,startedAtis a parseable ISO 8601 in the past and recent, uptime is a non-negative integer, uptime increases between two requests (1.1s sleep),nodematchesprocess.version/process.platform/process.arch,pidmatchesprocess.pid.packages/web/src/openapi/publicApiSchemas.ts—publicHealthResponseSchemaextended with the new fields. Backward-compatible additive change: existing consumers that only readstatuskeep working.docs/api-reference/sourcebot-public.openapi.json— regenerated viayarn workspace @sourcebot/web openapi:generateto match the schema.CHANGELOG.md—[Unreleased] → Addedentry noting the new fields and the backward-compatibility guarantee.Design decisions
startedAt, not per-requestnew Date(). Two reasons: (1) the field is genuinely the process start time, so per-request computation would be wrong; (2) caching it costs oneDate.now()call at module load and zero per-request. Theuptimefield is the only per-request computation, and it's a single subtract-and-floor.uptimeis 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.nodeobject, not flatnodeVersion/nodePlatform/nodeArchfields. Mirrors the standard Node.js process facts as a single block, so the response shape is grep-able forbody.node.versionregardless of how many more node-level fields are added later.pidis at the top level, not undernode. The PID is process-scoped, not Node-scoped, and matches whatpsand similar tools expose.SOURCEBOT_VERSIONconstant is already in@sourcebot/shared. Everything else comes fromprocess.*. A future PR could add aSOURCEBOT_COMMIT_SHAenv var injected at build time vianext.config.js, but that's a separate change.status: "ok"field is preserved at the top level. Existing OpenAPI consumers that only readstatuskeep working. Existing K8slivenessProbeconfigs 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, thePublicHealthResponsecomponent in the published doc now includes the new required properties.The 7 unrelated test failures in
yarn workspace @sourcebot/web test --run(in the EEchat/skills,chat/mcp, andaskmcp/*areas) are pre-existing — they fail withTypeError: (0, core_1.getEnv) is not a functionfrom@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. K8slivenessProbe.httpGetwith noexpectedStatusCodes) are unaffected. The new fields are additive.Out of scope (potential follow-ups, not in this PR)
SOURCEBOT_COMMIT_SHAenv var injected at build time vianext.config.jsand exposed asbody.commitso operators can match a running instance against a deployed image.?strict=truefollow-on to/api/healthanalogous 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)./api/diagnosticsendpoint that returns more detailed runtime state (memory, event-loop lag, etc.) for operators who want to dig deeper.Risks
startedAtis captured at module load. In a serverless / edge deployment the module might be re-loaded per request andstartedAtwould reflect only the most recent init. Sourcebot is not deployed that way today (it's a long-running Node process), so this is theoretical.node.versionexposes the runtime version, which is already visible via any unauthenticated error response. No new information leak.pidis the Node process PID. Same visibility story asnode.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/healthstill returnsstatus: "ok"for existing liveness checks, but the JSON body now includes operator-oriented process facts: Sourcebotversion,startedAt(ISO 8601, derived once from process boot viaprocess.uptime()), per-requestuptimein whole seconds,pid, and anodeblock (version,platform,arch).The public Zod/OpenAPI
PublicHealthResponseschema and regeneratedsourcebot-public.openapi.jsondocument 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
/api/healthresponse fields and timing-related expectations.