Skip to content

fix: detect git branch on detached-HEAD CI checkouts (SDK-7009)#63

Open
harshit-browserstack wants to merge 6 commits into
mainfrom
fix/sdk-7009-ci-branch-detection
Open

fix: detect git branch on detached-HEAD CI checkouts (SDK-7009)#63
harshit-browserstack wants to merge 6 commits into
mainfrom
fix/sdk-7009-ci-branch-detection

Conversation

@harshit-browserstack

@harshit-browserstack harshit-browserstack commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

What is this about?

Fixes new CI builds reporting no branch in their VCS info, causing them to drop out of branch-based dashboards and filters even when on master.

Root cause: CI checks out a commit SHA directly (git checkout <sha>), leaving a detached-HEAD repo (customer build runs in a container at git root /app). There, .git/HEAD holds a raw SHA instead of ref: refs/heads/<branch>, so git-repo-info returns branch: null. getGitMetaData() copied info.branch verbatim into version_control.branch with no fallback → payload carried branch: null. Local checkouts sit on a real branch, so they were unaffected — matching the ticket's two same-SHA snippets.

Fix: in getGitMetaData() (packages/browserstack-service/src/util.ts), when git-repo-info yields no branch, fall back to (1) the CI provider's branch env var — covering the CI systems getCiInfo() already detects, plus an explicit BROWSERSTACK_GIT_BRANCH override — then (2) a best-effort git for-each-ref --points-at HEAD backstop. Values are sanitized via normalizeBranchName(). Fully graceful; never throws.

Verified by running the real compiled getGitMetaData() (real git-repo-info@2.1.1, real fs) in a real detached-HEAD repo: before → branch: null (reproduced symptom), after → branch: "master". 3 unit tests added (getGitMetaData suite 5/5); lint + build (tsc) pass; no new regressions.

Related Jira task/s

SDK-7009

Release (mandatory for every PR — required for the ready-for-review label)

Version bump: (required — tick exactly one)

  • minor (backwards-compatible feature)
  • patch (bug fix or other small change)

Release notes type: (optional)

  • New Feature
  • Bug Fix
  • Other Improvement

Release notes (customer-facing): (optional but encouraged)

  • Fixed the git branch not being reported for builds run in CI (detached-HEAD checkouts), so builds again appear under branch-based filters and dashboards.

Release notes (internal): (required — engineer-facing; what actually changed / why)

  • getGitMetaData now falls back to CI branch env vars (+ BROWSERSTACK_GIT_BRANCH override) and a git for-each-ref --points-at HEAD backstop when git-repo-info returns no branch on a detached-HEAD checkout. Adds normalizeBranchName sanitizer. Changeset included (patch).

Checklist

  • Ready to review
  • Has it been tested locally?

PR Validations

Run Tests: Comment RUN_TESTS to trigger sanity tests.

CI systems check out a commit SHA directly (`git checkout <sha>`), leaving
the repo in a detached-HEAD state. There, `.git/HEAD` holds a raw SHA rather
than `ref: refs/heads/<branch>`, so git-repo-info returns no branch and
`getGitMetaData` reported `version_control.branch = null`. Builds then dropped
out of branch-based dashboards and filters even though they were on master.

Add a branch fallback in `getGitMetaData`: when git-repo-info yields no branch,
resolve it from the CI provider's branch env var (covering the CI systems
getCiInfo already detects, plus an explicit BROWSERSTACK_GIT_BRANCH override),
then from a best-effort `git for-each-ref --points-at HEAD` backstop. Values are
sanitized via normalizeBranchName (strips refs/heads|origin/, rejects unsafe
input). Fully graceful — never throws, degrades to the prior behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔴 SDK PR Review gate is red. Pending:

  • The SDK PR Review Agent has not reviewed the current head commit yet — run the SDK PR Review Agent.

It turns green once the latest SDK PR Review Agent run reports GTG on the current head commit. A native reviewer approval is separately required by branch protection before merge.

@github-actions

Copy link
Copy Markdown
Contributor

🔴 SDK PR Review gate is red. Pending:

  • The SDK PR Review Agent has not reviewed the current head commit yet — run the SDK PR Review Agent.

It turns green once the latest SDK PR Review Agent run reports GTG on the current head commit. A native reviewer approval is separately required by branch protection before merge.

@harshit-browserstack

harshit-browserstack commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator Author

Good to go

All prior findings are resolved. The one remaining item from an earlier round — CI_MARKER_KEYS in packages/browserstack-service/tests/util.test.ts potentially drifting from getCiInfo()'s real gating vars — was fixed:

  • CI_MARKER_KEYS is now exhaustive against every env var getCiInfo() gates on.
  • The two CI-dependent tests (ignores a generic BRANCH env var when NOT in a recognised CI, uses a generic GIT_BRANCH var only inside a recognised CI) now assert utils.getCiInfo() is null/non-null before the branch assertion — any future drift between the list and getCiInfo() fails loudly instead of silently passing.

Update on current head (c4891798): the only change since the prior approved head (c119ae03) is in packages/browserstack-service/src/util.tsgitForEachRef's execFile import moved from module-top to a lazy const { execFile } = await import('node:child_process') inside the function, fixing a CI test-isolation regression around module-load timing. Production branch-resolution behavior is byte-identical to the previously approved head. CI is green (Build & test node 18.20/20/22, Lint).

No blocking findings remain.

@github-actions

Copy link
Copy Markdown
Contributor

🔴 SDK PR Review gate is red. Pending:

  • The SDK PR Review Agent reported blocking findings (🔴) on the current head commit — fix them and re-run the agent.

It turns green once the latest SDK PR Review Agent run reports GTG on the current head commit. A native reviewer approval is separately required by branch protection before merge.

…tests

- BROWSERSTACK_GIT_BRANCH now overrides even a git-repo-info-resolved branch
  (moved ahead of info.branch), matching its documented "override" role.
- GitLab: prefer CI_COMMIT_BRANCH over CI_COMMIT_REF_NAME (the latter is the
  tag name on tag pipelines).
- Split CI branch env vars into provider-specific (trusted directly) vs generic
  (BRANCH/GIT_BRANCH/…, only trusted when getCiInfo() detects a CI) to stop a
  stray local env var being misreported as the branch.
- Add CodeFresh CF_BRANCH (parity with getCiInfo detection).
- git backstop: query refs/heads (lstrip=2) then refs/remotes (lstrip=3) as two
  ordered calls, yielding bare branch names for ANY remote (not just origin) and
  making heads-before-remotes explicit rather than relying on refspec order.
- normalizeBranchName: denylist unsafe/illegal ref chars instead of an
  allowlist, so legal names (e.g. containing '#') are no longer dropped; kept
  internal (un-exported) so this stays a patch.
- Tests: cover the git for-each-ref backstop (mocked spawnSync) and the
  null-result path; assert override precedence; all env mutations via a
  save/restore helper (try/finally) to prevent cross-test leakage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔴 SDK PR Review gate is red. Pending:

  • The SDK PR Review Agent has not reviewed the current head commit yet — run the SDK PR Review Agent.

It turns green once the latest SDK PR Review Agent run reports GTG on the current head commit. A native reviewer approval is separately required by branch protection before merge.

@github-actions

Copy link
Copy Markdown
Contributor

🔴 SDK PR Review gate is red. Pending:

  • The SDK PR Review Agent reported blocking findings (🔴) on the current head commit — fix them and re-run the agent.

It turns green once the latest SDK PR Review Agent run reports GTG on the current head commit. A native reviewer approval is separately required by branch protection before merge.

…op, tests

- getBranchFromGit no longer passes git-repo-info's `.root` (which can point at
  the main repo dir under a git worktree); the backstop runs in process.cwd(),
  the actual checkout being instrumented.
- Convert the git for-each-ref backstop from synchronous spawnSync to async
  promisified execFile, and make it lazy (query refs/heads, return early, only
  then refs/remotes) with a 3s timeout — no blocking subprocess on getGitMetaData's
  async hot path.
- Tests: add coverage for the generic-var anti-collision gate (BRANCH ignored
  outside a recognised CI; GIT_BRANCH honoured inside Jenkins), CI-specific vs
  generic priority, and the remotes fall-through; env helper now also neutralises
  CI-detection markers so getCiInfo() is deterministic under ambient CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔴 SDK PR Review gate is red. Pending:

  • The SDK PR Review Agent has not reviewed the current head commit yet — run the SDK PR Review Agent.

It turns green once the latest SDK PR Review Agent run reports GTG on the current head commit. A native reviewer approval is separately required by branch protection before merge.

@github-actions

Copy link
Copy Markdown
Contributor

🔴 SDK PR Review gate is red. Pending:

  • The SDK PR Review Agent reported blocking findings (🔴) on the current head commit — fix them and re-run the agent.

It turns green once the latest SDK PR Review Agent run reports GTG on the current head commit. A native reviewer approval is separately required by branch protection before merge.

Round-3 review: the test's CI_MARKER_KEYS list (used to force getCiInfo() null
in the generic-var-gating tests) didn't cover every branch the real getCiInfo()
checks (Concourse, Google Cloud, Shippable, some CodeBuild/Wercker vars), so a
stray ambient CI marker could silently flip those tests to the wrong branch.

- Complete CI_MARKER_KEYS against every gating var in getCiInfo().
- Add self-checking guards — assert utils.getCiInfo() is null (outside-CI test)
  / non-null (Jenkins test) before the branch assertion — so any future drift
  between the list and the real detector fails loudly instead of silently.

Test-only change; production code unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔴 SDK PR Review gate is red. Pending:

  • The SDK PR Review Agent has not reviewed the current head commit yet — run the SDK PR Review Agent.

It turns green once the latest SDK PR Review Agent run reports GTG on the current head commit. A native reviewer approval is separately required by branch protection before merge.

@github-actions

Copy link
Copy Markdown
Contributor

🟢 SDK PR Review gate is green — the SDK PR Review Agent has given a GTG for this PR (the ready-for-review label is present and the latest SDK PR Review Agent run reports success on the current head commit).

A native GitHub reviewer approval is still separately required by branch protection before this PR can merge — this check does not substitute for that.

@github-actions

Copy link
Copy Markdown
Contributor

🟢 SDK PR Review gate is green — the SDK PR Review Agent has given a GTG for this PR (the ready-for-review label is present and the latest SDK PR Review Agent run reports success on the current head commit).

A native GitHub reviewer approval is still separately required by branch protection before this PR can merge — this check does not substitute for that.

…d&test)

The module-top `import { execFile }` + `promisify(execFile)` broke every test
that partial-mocks `node:child_process` (e.g. tests/cli/frameworks/index.test.ts
mocks it as `{ spawn }` only) — `execFile` was undefined at util.ts load, so
those suites failed to load with "No execFile export is defined on the mock".

Import child_process lazily inside gitForEachRef (within its existing try/catch)
so module load never references execFile. It's only touched when the git
backstop actually runs (covered by util.test.ts's full mock); suites that never
hit the backstop are unaffected, and a missing/partial mock degrades to [].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔴 SDK PR Review gate is red. Pending:

  • The SDK PR Review Agent has not reviewed the current head commit yet — run the SDK PR Review Agent.

It turns green once the latest SDK PR Review Agent run reports GTG on the current head commit. A native reviewer approval is separately required by branch protection before merge.

@github-actions

Copy link
Copy Markdown
Contributor

🟢 SDK PR Review gate is green — the SDK PR Review Agent has given a GTG for this PR (the ready-for-review label is present and the latest SDK PR Review Agent run reports success on the current head commit).

A native GitHub reviewer approval is still separately required by branch protection before this PR can merge — this check does not substitute for that.

@github-actions

Copy link
Copy Markdown
Contributor

🟢 SDK PR Review gate is green — the SDK PR Review Agent has given a GTG for this PR (the ready-for-review label is present and the latest SDK PR Review Agent run reports success on the current head commit).

A native GitHub reviewer approval is still separately required by branch protection before this PR can merge — this check does not substitute for that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant