src/orchestrator/run-context.ts — git/CI/host capture
Purpose
Section titled “Purpose”The per-run context for the invocations header row and the telemetry
RunContextRecord: commit, branch, dirty, CI provider, host/os/arch —
and, for the telemetry schema’s multi-workspace story, a stable
workspace identity and the repository’s default branch.
Public surface
Section titled “Public surface”export interface GitContext { commitSha: string | null // HEAD's commit, or null outside a repo / on failure branch: string | null // HEAD's branch, or null when detached dirty: boolean | null // passed in by the caller; null when unknown}export interface CiContext { ci: boolean provider: string | null // 'github' | 'gitlab' | 'buildkite' | 'circleci' | 'generic' (bare `CI`), or null}export interface HostContext { host: string | null os: string arch: string}export interface WorkspaceIdentity { id: string // stable 16-hex id — the same for every checkout of the same repo name: string // the repo (or root dir) basename}
export function captureGitContext( workspaceRoot: string, dirty?: boolean | null, env?: NodeJS.ProcessEnv,): GitContextexport function captureDefaultBranch(env: NodeJS.ProcessEnv, workspaceRoot: string): string | nullexport function detectCi(env: NodeJS.ProcessEnv): CiContextexport function captureHostContext(): HostContextexport function normalizeRemoteUrl(raw: string): stringexport function captureWorkspaceIdentity(workspaceRoot: string): WorkspaceIdentitycaptureGitContext(root, dirty, env)— readsHEADfrom the.gitfiles first (a.gitdirectory or a linked worktree’sgitdir:file, a symbolic or detached HEAD, loose refs andpacked-refs): no spawn on a familiar layout. Anything unfamiliar falls back to ONEgit rev-parse HEAD --abbrev-ref HEADspawn (commit on line 1, branch on line 2), behind try/catch. A detached HEAD takes the branch the CI environment names, when one does.dirtyis passed in — the run’sGitFilesCachepopulate already rangit status --porcelain, so no second status spawn. Each field degrades to null independently.captureDefaultBranch(env, root)— the branch whose runs feed the shared scheduling baseline (an experiment on a branch must not count into main). Ladder: GitLab’sCI_DEFAULT_BRANCH; GitHub Actions’ event payload (repository.default_branch, one best-effort JSON read); elsegit symbolic-ref --short refs/remotes/origin/HEADwith theorigin/prefix stripped. Null when none resolve — the consumer then counts every run. Never throws.detectCi(env)— the provider matrix, first truthy variable wins (present and not0/false):github,gitlab,buildkite,circleci, and a bareCIasgeneric.captureHostContext()— hostname/os/arch.captureWorkspaceIdentity(root)— the same id from any machine’s checkout of the same repo: theoriginremote URL normalized (normalizeRemoteUrl:git@github.com:o/r.git,ssh://git@github.com/o/randhttps://github.com/o/r.gitall reduce togithub.com/o/r) and hashed; no remote → a salt persisted at<root>/.vx/workspace-id; an unwritable.vx/→ the root path itself. Onegitspawn behind try/catch, called only when telemetry is active. Never throws.
Invariants
Section titled “Invariants”- A plain run pays no git spawn here: the context reads
.gitdirectly, and the identity is captured only for a telemetry-bearing run (one spawn then, two if.gitis unfamiliar). Never fails a run.