Skip to content

feat(deploy): store git commit hash in deployment logs (EXSC-330)#2017

Open
0xDEnYO wants to merge 3 commits into
mainfrom
EXSC-330-git-commit-hash-deploy-logs
Open

feat(deploy): store git commit hash in deployment logs (EXSC-330)#2017
0xDEnYO wants to merge 3 commits into
mainfrom
EXSC-330-git-commit-hash-deploy-logs

Conversation

@0xDEnYO

@0xDEnYO 0xDEnYO commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Which Linear task belongs to this PR?

EXSC-330

Why did I implement it this way?

Deployment records (Mongo + local JSON mirror) had no link back to the exact codebase state that produced them, making it hard to check out and verify what was actually deployed. Rather than threading a commit-hash argument through every shell script and CLI call site, the hash is captured automatically inside the shared logging layer (getCurrentGitCommitHash() in mongo-log-utils.ts, git rev-parse HEAD under the hood) and injected by DeploymentLogger.log()/logBatch() and the update-deployment-logs.ts add CLI path. This keeps every existing caller unchanged and makes it impossible for a future deploy script to forget to pass it.

Backward compatibility: gitCommitHash/GIT_COMMIT_HASH is a new required field on IDeploymentRecord, but historical JSON records (which predate this field) parse fine — RecordTransformer.transformRawToDeployment treats GIT_COMMIT_HASH as optional on the raw JSON shape and defaults to '' when absent.

Follow-up from review: getCurrentGitCommitHash() originally fell back to '' when git rev-parse HEAD fails (e.g. running outside a git checkout), which would have been indistinguishable from the "predates this field" default above and could silently look like expected legacy data on a later audit. Changed the failure fallback to the sentinel 'UNKNOWN' so a real capture failure stays visible instead of blending in.

Checklist before requesting a review

  • I have performed a self-review of my code
  • This pull request is as small as possible and only tackles one problem
  • I have run /pr-ready (local CodeRabbit) on this branch and resolved (or explicitly documented) all findings — see .agents/commands/pr-ready.md
    • coderabbit review --base origin/main --type committed --plainno findings (initial pass). PR_READY_OK=1 bypass used on gh pr create only because the pre-PR gate hook misfires when run from a git worktree (known issue, unrelated to review outcome); the review itself was clean.
    • Cloud CodeRabbit then flagged one legitimate finding on the pushed diff (deployment-logger.test.ts: createMockDeployment's overrides param didn't exclude gitCommitHash, letting a test caller inject a custom hash despite the return type omitting it). Fixed in a dedicated pr-ready: commit, verified via tsc-files + bun test (31/31 pass). The scoped local re-run (--base-commit) hit CodeRabbit's shared org rate limit before running; PR_READY_OK=1 bypass used on the follow-up git push for that reason. Cloud CodeRabbit will re-review the pushed fix commit as the safety net.
  • I have added tests that cover the functionality / test the bug
  • For new facets: I have checked all points from this list: https://www.notion.so/lifi/New-Facet-Contract-Checklist-157f0ff14ac78095a2b8f999d655622e
  • I have updated any required documentation

Checklist for reviewer (DO NOT DEPLOY and contracts BEFORE CHECKING THIS!!!)

  • I have checked that any arbitrary calls to external contracts are validated and or restricted
  • I have checked that any privileged calls (i.e. storage modifications) are validated and or restricted
  • I have ensured that any new contracts have had AT A MINIMUM 1 preliminary audit conducted on by <company/auditor>

Captures the local codebase's git commit hash automatically inside the
deployment logger (Mongo + local JSON mirror), so every deployment
record can be tied back to the exact code that was deployed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@lifi-action-bot lifi-action-bot marked this pull request as draft July 3, 2026 03:12
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR adds git commit hash tracking to deployment log records. A new getCurrentGitCommitHash() helper reads the current HEAD via git rev-parse HEAD. IDeploymentRecord/IRawDeploymentData gain a gitCommitHash/GIT_COMMIT_HASH field, populated through DeploymentLogger, update-deployment-logs.ts upserts, and the record transformer, with tests updated accordingly.

Changes

Deployment record git commit hash tracking

Layer / File(s) Summary
Schema field and commit hash helper
script/deploy/shared/mongo-log-utils.ts
Adds gitCommitHash/GIT_COMMIT_HASH fields to IDeploymentRecord/IRawDeploymentData, introduces getCurrentGitCommitHash() using execSync, extends equality filter keys, and populates gitCommitHash (defaulting to '') in the raw-to-record transformer.
DeploymentLogger wiring
script/deploy/shared/deployment-logger.ts
log/logBatch/logDeployment/logDeploymentBatch omit gitCommitHash from caller input and populate it via getCurrentGitCommitHash() (once per batch for logBatch); local JSON output now includes GIT_COMMIT_HASH.
update-deployment-logs.ts upsert integration
script/deploy/update-deployment-logs.ts
upsertDeployment and batchUpsertDeployments include gitCommitHash in $set updates; the add CLI subcommand sets gitCommitHash via getCurrentGitCommitHash().
Test helper updates
script/deploy/shared/batch-deployment-queries.test.ts, script/deploy/shared/deployment-logger.test.ts
makeRecord sets gitCommitHash on mock records; createMockDeployment's overrides type omits gitCommitHash.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

  • lifinance/contracts#1229: Both PRs modify the deployment log management flow in script/deploy/update-deployment-logs.ts.
  • lifinance/contracts#1666: Both PRs modify the shared mongo-log-utils equality filter/upsert matching logic that the new gitCommitHash field integrates into.
  • lifinance/contracts#1896: Both PRs update the mocked deployment record shape used by batch query tests.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely summarizes the main change: storing git commit hashes in deployment logs.
Description check ✅ Passed The description follows the template well, includes the Linear task, rationale, and checklist items, and is sufficiently complete.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch EXSC-330-git-commit-hash-deploy-logs

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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
script/deploy/shared/mongo-log-utils.ts (4)

518-519: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate raw-data shape risks drift.

The validData assertion type inside transformRawToDeployment re-declares the same fields as IRawDeploymentData rather than reusing it (e.g. Pick/Omit on the interface). This PR adds GIT_COMMIT_HASH?/gitCommitHash in both places; keeping them in sync manually for every future field is easy to get wrong. Backward-compat handling itself (optional field, default '') is correctly done. As per path instructions, "explicitly treat it as an upgrade/storage-like contract and preserve compatibility (e.g., optional fields when reading older records)" — that part is satisfied here.

Also applies to: 550-561, 578-579

🤖 Prompt for 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.

In `@script/deploy/shared/mongo-log-utils.ts` around lines 518 - 519, The raw
deployment shape is duplicated in transformRawToDeployment, which risks future
drift between validData and IRawDeploymentData. Update the validData assertion
to reuse the existing IRawDeploymentData contract via Pick/Omit (or equivalent)
instead of re-declaring fields like GIT_COMMIT_HASH/gitCommitHash, while keeping
the backward-compatible optional read and default-empty-string behavior intact.

Source: Path instructions


85-97: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

No indication when the working tree is dirty.

The stated goal is linking deployment records "back to the exact code state that produced them," but git rev-parse HEAD only reflects the last commit. If there are uncommitted local changes at deploy time (a common scenario during iterative deploys), the stored hash won't actually match the deployed bytecode, which undermines the traceability the feature is meant to provide.

🔍 Optional dirty-tree indicator
 export function getCurrentGitCommitHash(): string {
   try {
-    return execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim()
+    const hash = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim()
+    const isDirty = execSync('git status --porcelain', {
+      encoding: 'utf8',
+    }).trim().length > 0
+    return isDirty ? `${hash}-dirty` : hash
   } catch (error) {
     consola.warn(`Failed to determine git commit hash: ${error}`)
     return ''
   }
 }
🤖 Prompt for 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.

In `@script/deploy/shared/mongo-log-utils.ts` around lines 85 - 97, The git hash
captured by getCurrentGitCommitHash only reflects HEAD and misses uncommitted
changes, so update the logic to detect a dirty working tree before returning the
identifier. Use the existing getCurrentGitCommitHash helper to also check git
status in the same flow, and when the tree is dirty append a clear dirty-tree
marker to the returned value so deployment records still map to the exact code
state. Keep the warning path for failures, but make sure the returned identifier
from getCurrentGitCommitHash distinguishes clean vs dirty states.

1-2: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Consider execFileSync over execSync for the git call.

Static analysis flags child_process usage broadly, but the command here ('git rev-parse HEAD') is a fully static string with no interpolated input, so there's no actual injection risk. Still, using execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }) avoids shell parsing entirely and is the more idiomatic/hardened pattern for fixed-argument subprocess calls.

Also applies to: 90-97

🤖 Prompt for 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.

In `@script/deploy/shared/mongo-log-utils.ts` around lines 1 - 2, The git hash
lookup in the shared Mongo log utility should avoid shell parsing by switching
from execSync to the fixed-argument subprocess form. Update the helper in
mongo-log-utils.ts that runs the git rev-parse HEAD command to use execFileSync
with git and its arguments, keeping the UTF-8 output handling the same.
Reference the existing git lookup helper and the child_process import so the
change is applied consistently.

Source: Linters/SAST tools


90-97: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Memoize the git commit hash to avoid repeated subprocess spawns.

logBatch() already computes this once per batch (see deployment-logger.ts:210), but log() invokes getCurrentGitCommitHash() on every call (deployment-logger.ts:125). Since HEAD cannot change mid-script-execution, callers that log many individual deployments in one run (e.g. per-network loops) will spawn a git subprocess for every single call. Caching the result at module scope would eliminate this redundant I/O.

⚡ Proposed memoization
+let cachedGitCommitHash: string | undefined
+
 export function getCurrentGitCommitHash(): string {
+  if (cachedGitCommitHash !== undefined) return cachedGitCommitHash
   try {
-    return execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim()
+    cachedGitCommitHash = execSync('git rev-parse HEAD', {
+      encoding: 'utf8',
+    }).trim()
   } catch (error) {
     consola.warn(`Failed to determine git commit hash: ${error}`)
-    return ''
+    cachedGitCommitHash = ''
   }
+  return cachedGitCommitHash
 }
🤖 Prompt for 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.

In `@script/deploy/shared/mongo-log-utils.ts` around lines 90 - 97, The git commit
hash lookup is spawning a subprocess on every call to getCurrentGitCommitHash(),
which is repeatedly invoked by log() even though the value is stable for the
script run. Memoize the result at module scope in mongo-log-utils.ts so the
first successful execSync('git rev-parse HEAD') is cached and reused by
subsequent calls, while preserving the existing fallback behavior in the catch
path. Keep the change centered around getCurrentGitCommitHash() so callers like
deployment-logger’s log() and logBatch() automatically benefit without extra
changes.
🤖 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 `@script/deploy/shared/deployment-logger.test.ts`:
- Around line 50-55: `createMockDeployment` still lets callers pass
`gitCommitHash` through `overrides`, even though the helper’s return type omits
it. Update the `overrides` parameter type to also exclude `gitCommitHash`
(matching the existing `Omit` on the return type), so `createMockDeployment`
cannot be used to inject a custom hash and the helper invariant stays enforced.

---

Nitpick comments:
In `@script/deploy/shared/mongo-log-utils.ts`:
- Around line 518-519: The raw deployment shape is duplicated in
transformRawToDeployment, which risks future drift between validData and
IRawDeploymentData. Update the validData assertion to reuse the existing
IRawDeploymentData contract via Pick/Omit (or equivalent) instead of
re-declaring fields like GIT_COMMIT_HASH/gitCommitHash, while keeping the
backward-compatible optional read and default-empty-string behavior intact.
- Around line 85-97: The git hash captured by getCurrentGitCommitHash only
reflects HEAD and misses uncommitted changes, so update the logic to detect a
dirty working tree before returning the identifier. Use the existing
getCurrentGitCommitHash helper to also check git status in the same flow, and
when the tree is dirty append a clear dirty-tree marker to the returned value so
deployment records still map to the exact code state. Keep the warning path for
failures, but make sure the returned identifier from getCurrentGitCommitHash
distinguishes clean vs dirty states.
- Around line 1-2: The git hash lookup in the shared Mongo log utility should
avoid shell parsing by switching from execSync to the fixed-argument subprocess
form. Update the helper in mongo-log-utils.ts that runs the git rev-parse HEAD
command to use execFileSync with git and its arguments, keeping the UTF-8 output
handling the same. Reference the existing git lookup helper and the
child_process import so the change is applied consistently.
- Around line 90-97: The git commit hash lookup is spawning a subprocess on
every call to getCurrentGitCommitHash(), which is repeatedly invoked by log()
even though the value is stable for the script run. Memoize the result at module
scope in mongo-log-utils.ts so the first successful execSync('git rev-parse
HEAD') is cached and reused by subsequent calls, while preserving the existing
fallback behavior in the catch path. Keep the change centered around
getCurrentGitCommitHash() so callers like deployment-logger’s log() and
logBatch() automatically benefit without extra changes.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 160546df-9b38-4e03-a2c1-b42c87cf3963

📥 Commits

Reviewing files that changed from the base of the PR and between a5bd281 and f4b0644.

📒 Files selected for processing (5)
  • script/deploy/shared/batch-deployment-queries.test.ts
  • script/deploy/shared/deployment-logger.test.ts
  • script/deploy/shared/deployment-logger.ts
  • script/deploy/shared/mongo-log-utils.ts
  • script/deploy/update-deployment-logs.ts

Comment thread script/deploy/shared/deployment-logger.test.ts
…ash (script/deploy/shared/deployment-logger.test.ts:50)

CodeRabbit: the overrides param still accepted Partial<IDeploymentRecord>,
letting callers inject a custom gitCommitHash even though the return type
omits it — narrowed overrides to the same Omit as the return type.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@0xDEnYO 0xDEnYO marked this pull request as ready for review July 3, 2026 03:24
@0xDEnYO 0xDEnYO enabled auto-merge (squash) July 3, 2026 03:24
An empty string on execSync failure was indistinguishable from the
default used for records logged before this field existed, so a
failed capture (e.g. running outside a git checkout) could silently
look like expected legacy data instead of surfacing as a real gap in
deployment provenance. UNKNOWN is unambiguous either way.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

2 participants