-
Notifications
You must be signed in to change notification settings - Fork 16
feat(LAM-1978): auto-capture git state into trace metadata #311
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
laminar-coding-agent
wants to merge
2
commits into
main
Choose a base branch
from
feat/lam-1978-git-metadata
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| """Best-effort collection of git state for trace metadata. | ||
|
|
||
| Collected once per process at `Laminar.initialize()` and merged into the | ||
| global trace metadata at the LOWEST precedence, so both `LMNR_TRACE_METADATA` | ||
| and the explicit `metadata=` init argument can override any of the keys. The | ||
| keys land on every span as `lmnr.association.properties.metadata.git.*` and | ||
| flow into `traces.metadata` server-side with no backend changes. | ||
|
|
||
| Collection must NEVER crash or block initialization: every git subprocess is | ||
| wrapped in a broad try/except with a short timeout, and a gitless environment | ||
| (CI checkout without `.git`, git binary missing, sandboxed runtime) degrades | ||
| to well-known CI environment variables, then to nothing. | ||
|
|
||
| Set `LMNR_DISABLE_GIT_METADATA` to a truthy value ("true", "1", "yes", "on") | ||
| to disable collection entirely. | ||
|
|
||
| This is a cross-language parity surface with the TS SDK | ||
| `lmnr-ts/packages/lmnr/src/git-metadata.ts` — keep the two line-comparable. | ||
| """ | ||
|
|
||
| import os | ||
| import subprocess | ||
| from functools import lru_cache | ||
|
|
||
| from lmnr.sdk.debug.config import _is_truthy | ||
|
|
||
| GIT_COMMIT_METADATA_KEY = "git.commit" | ||
| GIT_BRANCH_METADATA_KEY = "git.branch" | ||
| GIT_DIRTY_METADATA_KEY = "git.dirty" | ||
|
|
||
| _GIT_TIMEOUT_SECONDS = 1.5 | ||
|
|
||
| # (commit env var, branch env var) per CI/deploy platform, checked in order. | ||
| # Used only when git itself is unavailable — e.g. gitless CI checkouts. | ||
| _CI_ENV_VARS: list[tuple[str, str]] = [ | ||
| ("GITHUB_SHA", "GITHUB_REF_NAME"), | ||
| ("VERCEL_GIT_COMMIT_SHA", "VERCEL_GIT_COMMIT_REF"), | ||
| ("CI_COMMIT_SHA", "CI_COMMIT_REF_NAME"), | ||
| ("CIRCLE_SHA1", "CIRCLE_BRANCH"), | ||
| ("RENDER_GIT_COMMIT", "RENDER_GIT_BRANCH"), | ||
| ("RAILWAY_GIT_COMMIT_SHA", "RAILWAY_GIT_BRANCH"), | ||
| ] | ||
|
|
||
|
|
||
| # Process-level opt-out recorded by `Laminar.initialize(disable_git_metadata= | ||
| # True)`. Kept here (not on the Laminar class) so EVERY collection point — | ||
| # global trace metadata AND eval run metadata — honors the same flag without | ||
| # re-plumbing the init argument through each call site. | ||
| _disabled: bool = False | ||
|
|
||
|
|
||
| def set_git_metadata_disabled(disabled: bool) -> None: | ||
| """Record the initialize()-time opt-out for later collection points.""" | ||
| global _disabled | ||
| _disabled = disabled | ||
|
|
||
|
|
||
| def _git_metadata_disabled() -> bool: | ||
| return _disabled or _is_truthy(os.environ.get("LMNR_DISABLE_GIT_METADATA")) | ||
|
|
||
|
|
||
| def _run_git(*args: str) -> str | None: | ||
| """Run a git command; return its stripped stdout, None on ANY failure.""" | ||
| try: | ||
| result = subprocess.run( | ||
| ["git", *args], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=_GIT_TIMEOUT_SECONDS, | ||
| ) | ||
| except Exception: | ||
| return None | ||
| if result.returncode != 0: | ||
| return None | ||
| return result.stdout.strip() | ||
|
|
||
|
|
||
| def collect_git_metadata() -> dict[str, str | bool]: | ||
| """Collect `git.commit` / `git.branch` / `git.dirty`, best-effort. | ||
|
|
||
| Returns {} when collection is disabled — via the `disable_git_metadata` | ||
| argument to `Laminar.initialize()` or the LMNR_DISABLE_GIT_METADATA env | ||
| var. The disabled check runs on every call (NOT inside the cache) so an | ||
| opt-out recorded after a prior collection still applies. | ||
|
|
||
| `git.branch` is omitted on a detached HEAD, and `git.dirty` counts only | ||
| tracked-file changes (untracked build artifacts should not flip it). | ||
| """ | ||
| if _git_metadata_disabled(): | ||
| return {} | ||
| return _collect_git_metadata_cached() | ||
|
|
||
|
|
||
| def reset_git_metadata_cache() -> None: | ||
| """Reset the process-level collection cache. Exposed for tests only.""" | ||
| _collect_git_metadata_cached.cache_clear() | ||
|
|
||
|
|
||
| @lru_cache(maxsize=1) | ||
| def _collect_git_metadata_cached() -> dict[str, str | bool]: | ||
| """The actual collection, cached for the process lifetime (git state is | ||
| fixed once the process is running; re-running subprocesses per | ||
| initialize()/evaluate() call would only add latency). Tests that vary cwd | ||
| or env must call `reset_git_metadata_cache()`. | ||
| """ | ||
| metadata: dict[str, str | bool] = {} | ||
| commit = _run_git("rev-parse", "HEAD") | ||
| if commit: | ||
| metadata[GIT_COMMIT_METADATA_KEY] = commit | ||
| branch = _run_git("rev-parse", "--abbrev-ref", "HEAD") | ||
| if branch and branch != "HEAD": | ||
| metadata[GIT_BRANCH_METADATA_KEY] = branch | ||
| status = _run_git("status", "--porcelain", "--untracked-files=no") | ||
| if status is not None: | ||
| metadata[GIT_DIRTY_METADATA_KEY] = bool(status) | ||
| return metadata | ||
|
|
||
| # Not a git repo, no git binary, or an unborn HEAD — fall back to CI env | ||
| # vars (a gap in Braintrust's approach: gitless CI checkouts get nothing). | ||
| for commit_var, branch_var in _CI_ENV_VARS: | ||
| env_commit = os.environ.get(commit_var) | ||
| if env_commit: | ||
| metadata[GIT_COMMIT_METADATA_KEY] = env_commit | ||
| env_branch = os.environ.get(branch_var) | ||
| if env_branch: | ||
| metadata[GIT_BRANCH_METADATA_KEY] = env_branch | ||
| break | ||
| return metadata |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.