Skip to content

PER-10496 feat: emit machine identity for slow-build diagnostics - #2384

Open
Shivanshu-07 wants to merge 5 commits into
masterfrom
feat/per-10496-machine-identity
Open

PER-10496 feat: emit machine identity for slow-build diagnostics#2384
Shivanshu-07 wants to merge 5 commits into
masterfrom
feat/per-10496-machine-identity

Conversation

@Shivanshu-07

@Shivanshu-07 Shivanshu-07 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

CLI side of Build-processing diagnostics (slow builds) — PER-10496. Pairs with percy-api https://github.com/percy/percy-api/pull/6715, which validates and consumes everything sent here for per-machine liveness on parallel builds (naming the dead CI machine when one stops sending snapshots).

  • env.machine getter (@percy/env): { id, hostname, runUrl } — sanitized-hostname id suffixed with the CI node index where the provider exposes one (CIRCLE_NODE_INDEX, BUILDKITE_PARALLEL_JOB), and the CI run URL for circle/buildkite/github/gitlab. Deliberately excluded from the env getter debug logging (like token) — those debug logs are uploaded with build logs, and hostnames should not ride along.
  • Build create (@percy/client): machine-id / machine-hostname / machine-ci-run-url attributes (only when an id exists).
  • X-Percy-Machine-Id header on snapshot-create and build-finalize calls, attached per-call via machineHeaders() — never in headers(), so it cannot leak to off-domain requests (validateDomain).

Server-side: percy-api enforces charset/length limits and an https + CI-host allowlist on the URL, and treats all of it as advisory. Old CLI versions degrade gracefully (the feature falls back to non-machine-specific messaging).

Testing

  • @percy/env: 5 new specs (sanitized id, node-index suffix + run URL on circle, composed GitHub Actions run URL, null run URL fallback, debug-logging exclusion). Suite green (128/128).
  • @percy/client: header specs for snapshot + finalize, machine attributes on build create; existing exact-payload expectations updated. Suite green under node 14 except one pre-existing environmental failure (proxy getProxy should return undefined if no proxy is set) that also fails on a clean checkout on this machine (local proxy present) — unrelated to this change.

Post-Deploy Monitoring & Validation

  • What to monitor: percy-api warn logs "machine identity ingest skipped" (should stay ≈0); Redis key growth percy:api:build-machines:* (bounded by active builds, 25h TTL).
  • Validation: run a parallel build with this CLI against an org with the slow-build-diagnostics flag ON → build show JSON carries machine facts in diagnosis; kill one shard → machine named in the receiving-state alert.
  • Expected healthy behavior: zero change for orgs with the flag off; no additional CLI network calls (attributes/header ride existing requests).
  • Failure signal / rollback: any snapshot POST regression correlated with the header → revert this PR; the API ignores absent identity entirely.
  • Window & owner: first minor release post-merge, @shivanshusingh.

🤖 Generated with Claude Code

New env.machine getter (hostname-based id with CI node-index suffix,
per-provider run URL) sent as build-create attributes plus an
X-Percy-Machine-Id header on snapshot/finalize calls so percy-api can
track per-machine liveness on parallel builds and name the dead
machine when one stops sending.

The getter is excluded from env debug logging (those logs are uploaded
with build logs) and the header is attached per-call, never in
headers(), so it cannot reach off-domain requests. percy-api validates
everything server-side; old CLIs degrade gracefully.
@Shivanshu-07
Shivanshu-07 requested a review from a team as a code owner August 13, 2026 18:25
@percy/env enforces 100% line and branch coverage; the new machine
getter left the buildkite/gitlab cases, the hostname failure path and
the optional-var fallbacks uncovered.
@percy/client also enforces 100% branch coverage; the build-attribute
spread and machineHeaders() fallbacks (old CLI / no resolvable hostname)
were untested.
@Shivanshu-07

Copy link
Copy Markdown
Contributor Author

CI is green (30/30). For the record on the two reds seen along the way:

  • Test @percy/env and Test @percy/client were mine: both packages enforce 100% line and branch coverage via test:coverage (a different command from test, which is what I'd run locally), and the new machine getter left the buildkite/gitlab cases, the hostname-failure path, and the no-machine-identity fallbacks uncovered. Fixed in ee8b24d and 3c6ceff — both packages now report 100/100/100/100.
  • Test @percy/core (Windows leg only) failed on should retry by default on the snapshot discovery upto 3 times, which observed 2 retry log lines instead of 3. That is a timing assertion in packages/core; this PR touches only packages/env and packages/client, and the Linux core leg passed on the same commit. Re-ran the job and it passed, so it was flaky rather than related.

Shivanshu-07 and others added 2 commits September 1, 2026 18:04
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d ids on gitlab/jenkins, buildkite job anchor

Review follow-ups on the machine-identity getter:

- Sanitize the CI shard index like the hostname: the id is an HTTP header value on
  every snapshot POST, and a stray newline in CIRCLE_NODE_INDEX rejected the upload.
- Suffix GitLab CI_NODE_INDEX and Jenkins EXECUTOR_NUMBER so several shards on one
  host stop sharing a machine id (a dead shard looked alive while a sibling uploaded).
- Anchor the Buildkite run url to BUILDKITE_JOB_ID so the link lands on the dead job.
- Cap the id at the API's 128 chars; drop an id that sanitizes to dashes alone
  (non-ASCII hostnames collided); send null instead of the CI/unknown marker.
- Tests: one machineAttrs mapping in client.test.js instead of nine pasted copies.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Central YAML (base), Workspace UI (inherited)

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: b45f557e-565a-4057-8a76-1d06ebddcd84

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@Shivanshu-07

Copy link
Copy Markdown
Contributor Author

Claude Code PR Review

PR: #2384Head: 7f69e07Reviewers: fallback inline checklist (built-in code-review: line-by-line scan + reuse finder)

Summary

Adds machine identity to @percy/env (env.machine: sanitized hostname id with a per-shard suffix, hostname, CI run URL, CI platform) and sends it as build attributes on create plus an X-Percy-Machine-Id header on snapshot POST and finalize, so percy-api can detect a parallel CI machine that stopped responding.

Review Table

Priority Category Check Status Notes
High Security No hardcoded secrets or credentials Pass
High Security Authentication/authorization checks present N/A Client-side only; identity is validated and identity-gated server-side
High Security Input validation and sanitization Pass Hostname and shard index are both sanitized to the header-safe alphabet and capped at 128 chars (fixed in 7f69e07)
High Security No IDOR — resource ownership validated N/A
High Security No SQL injection (parameterized queries) N/A
High Correctness Logic is correct, handles edge cases Pass Zero shard index kept; non-identifying (all-dash) ids dropped; unknown CI sends null platform (fixed in 7f69e07)
High Correctness Error handling is explicit, no swallowed exceptions Pass os.hostname() failure degrades to null identity deliberately
High Correctness No race conditions or concurrency issues N/A
Medium Testing New code has corresponding tests Pass 146/146 env specs, 100% line+branch coverage
Medium Testing Error paths and edge cases tested Pass Header-safety, cap, all-dash id, CI/unknown, missing job id covered
Medium Testing Existing tests still pass (no regressions) Pass @percy/client suite green except the pre-existing getProxy spec, which fails identically on master on this machine (local system proxy)
Medium Performance No N+1 queries or unbounded data fetching N/A
Medium Performance Long-running tasks use background jobs N/A
Medium Quality Follows existing codebase patterns Pass `
Medium Quality Changes are focused (single concern) Pass
Low Quality Meaningful names, no dead code Pass Dead || null on platform removed
Low Quality Comments explain why, not what Pass
Low Quality No unnecessary dependencies added Pass

Findings

Fixed in 7f69e07 (were open at 6dc6677):

  • File: packages/env/src/environment.js:442Severity: High — the CI node index was appended to the id unsanitized; the id is the X-Percy-Machine-Id header value, so a stray newline in CIRCLE_NODE_INDEX rejected every snapshot POST. Now sanitized with the same alphabet.
  • File: packages/env/src/environment.js:420Severity: Medium — only Circle and Buildkite got a shard suffix, so GitLab parallel: jobs and Jenkins executors on one host shared an id and a dead shard looked alive. CI_NODE_INDEX / EXECUTOR_NUMBER now suffix the id.
  • File: packages/env/src/environment.js:427Severity: Medium — Buildkite sent the build-level URL, identical for every parallel job. Now anchored with #BUILDKITE_JOB_ID.
  • File: packages/env/src/environment.js:450Severity: Low — platform passed CI/unknown through. Now null; server pattern would have dropped it anyway.
  • File: packages/env/src/environment.js:441Severity: Low — no length cap; non-ASCII hostnames sanitized to dashes alone and collided. Capped at 128, all-dash ids dropped.
  • File: packages/client/test/client.test.js:238Severity: Low — the four machine wire keys were pasted into nine expectations. One machineAttrs mapping now.

Open, left as a product call:

  • File: packages/client/src/client.js:388
  • Severity: Medium
  • Reviewer: built-in code-review
  • Issue: Machine attributes (including the raw hostname) are sent for local, non-CI runs too, so a developer laptop hostname reaches the API on every percy exec.
  • Suggestion: Gate on env.machine.platform (CI detected) if product agrees local runs should not register machines. The API already hides hostnames from non-members, so this is exposure to Percy, not to other users.

Not applied: merging the per-provider machine specs into the existing provider fixture files (churn across five test files for no behaviour change).


Verdict: PASS

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