Skip to content

fix(crafter): use actual PR head commit instead of merge commit in GitHub Actions#3066

Open
waveywaves wants to merge 1 commit intochainloop-dev:mainfrom
waveywaves:fix/github-pr-merge-commit-sha
Open

fix(crafter): use actual PR head commit instead of merge commit in GitHub Actions#3066
waveywaves wants to merge 1 commit intochainloop-dev:mainfrom
waveywaves:fix/github-pr-merge-commit-sha

Conversation

@waveywaves
Copy link
Copy Markdown
Contributor

Summary

Fixes #3064.

GitHub Actions creates a temporary merge commit for pull_request events, so .git/HEAD points to that synthetic commit. This causes attestation init to record a ghost commit SHA in git.head that doesn't exist on any branch — breaking the referral graph when cross-referencing with local or agentic attestations.

Root cause

gracefulGitRepoHead() reads repo.Head() directly via go-git, which in a GH Actions PR checkout returns the merge commit. The CI runner already captures GITHUB_EVENT_NAME and GITHUB_EVENT_PATH but these weren't used to correct the git HEAD.

Fix

After gracefulGitRepoHead() resolves HEAD, check if we're in a GitHub Actions pull_request or pull_request_target event. If so, read the actual PR head SHA from the event payload (pull_request.head.sha) and look up that commit in the local repo instead.

  • New file: pkg/attestation/crafter/cioverride.go
    • resolveGitHubPRHeadSHA() — reads event JSON, extracts SHA
    • resolveCommitByHash() — opens repo, looks up commit by hash
  • Modified: pkg/attestation/crafter/crafter.goinitialCraftingState now calls the override after gracefulGitRepoHead
  • Falls back gracefully: if the override SHA can't be resolved (shallow clone, missing object), keeps the merge commit and logs a warning

Design choices

  • Separate file (cioverride.go) keeps CI-specific logic out of the general-purpose crafter.go
  • Reads env vars directly rather than going through the runner interface — GITHUB_EVENT_NAME and GITHUB_EVENT_PATH only exist in GitHub Actions, so a runner-type check would be redundant
  • Re-opens the repo instead of threading the repo handle from gracefulGitRepoHead — the perf cost is negligible for a one-time att init operation, and it keeps the function signatures simple

Future extensibility

The same pattern can be extended for GitLab MR pipelines (CI_MERGE_REQUEST_SOURCE_BRANCH_SHA) by adding another resolver in cioverride.go.

Test plan

  • go build ./pkg/attestation/crafter/... passes
  • 6 table-driven subtests for resolveGitHubPRHeadSHA: PR event, PR target event, push event, no event, malformed JSON, missing head SHA
  • gofmt -l clean
  • Commits GPG-signed and DCO signed-off

🤖 Generated with Claude Code

…tHub Actions

GitHub Actions creates a temporary merge commit for pull_request events,
so .git/HEAD (and GITHUB_SHA) points to that synthetic commit instead of
the actual PR branch head. This causes the attestation to reference a
ghost commit that doesn't exist on any branch, breaking the referral
graph when cross-referencing with local or agentic attestations.

Read the actual PR head SHA from the GitHub event payload
(pull_request.head.sha in GITHUB_EVENT_PATH) and resolve that commit
from the local repo instead. Falls back gracefully to the merge commit
if the override SHA can't be resolved.

Fixes: chainloop-dev#3064

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Vibhav Bobade <vibhav.bobde@gmail.com>
@waveywaves waveywaves force-pushed the fix/github-pr-merge-commit-sha branch from f7f19ae to 13b5e95 Compare April 21, 2026 10:28
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="pkg/attestation/crafter/cioverride.go">

<violation number="1" location="pkg/attestation/crafter/cioverride.go:85">
P2: New CI override duplicates HEAD commit assembly logic with different error handling than `gracefulGitRepoHead`, risking inconsistent attestation metadata across execution contexts.</violation>
</file>

<file name="pkg/attestation/crafter/crafter.go">

<violation number="1" location="pkg/attestation/crafter/crafter.go:444">
P2: Nil-pointer panic risk: added error logging path dereferences `opts.Logger` without checking for nil.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread pkg/attestation/crafter/cioverride.go Outdated
return nil, err
}

c := &HeadCommit{
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 21, 2026

Choose a reason for hiding this comment

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

P2: New CI override duplicates HEAD commit assembly logic with different error handling than gracefulGitRepoHead, risking inconsistent attestation metadata across execution contexts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pkg/attestation/crafter/cioverride.go, line 85:

<comment>New CI override duplicates HEAD commit assembly logic with different error handling than `gracefulGitRepoHead`, risking inconsistent attestation metadata across execution contexts.</comment>

<file context>
@@ -0,0 +1,118 @@
+		return nil, err
+	}
+
+	c := &HeadCommit{
+		Hash:        commit.Hash.String(),
+		AuthorEmail: commit.Author.Email,
</file context>
Fix with Cubic

Comment thread pkg/attestation/crafter/crafter.go Outdated
if actualSHA := resolveGitHubPRHeadSHA(); actualSHA != "" && actualSHA != headCommit.Hash {
override, err := resolveCommitByHash(cwd, actualSHA, opts.Logger)
if err != nil {
opts.Logger.Warn().Err(err).Str("sha", actualSHA).Msg("could not resolve PR head commit, keeping merge commit")
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 21, 2026

Choose a reason for hiding this comment

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

P2: Nil-pointer panic risk: added error logging path dereferences opts.Logger without checking for nil.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pkg/attestation/crafter/crafter.go, line 444:

<comment>Nil-pointer panic risk: added error logging path dereferences `opts.Logger` without checking for nil.</comment>

<file context>
@@ -433,6 +433,21 @@ func initialCraftingState(cwd string, opts *InitOpts) (*api.CraftingState, error
+		if actualSHA := resolveGitHubPRHeadSHA(); actualSHA != "" && actualSHA != headCommit.Hash {
+			override, err := resolveCommitByHash(cwd, actualSHA, opts.Logger)
+			if err != nil {
+				opts.Logger.Warn().Err(err).Str("sha", actualSHA).Msg("could not resolve PR head commit, keeping merge commit")
+			} else {
+				headCommit = override
</file context>
Suggested change
opts.Logger.Warn().Err(err).Str("sha", actualSHA).Msg("could not resolve PR head commit, keeping merge commit")
if opts.Logger != nil {
opts.Logger.Warn().Err(err).Str("sha", actualSHA).Msg("could not resolve PR head commit, keeping merge commit")
}
Fix with Cubic

@waveywaves waveywaves marked this pull request as ready for review April 21, 2026 16:18
@waveywaves
Copy link
Copy Markdown
Contributor Author

@migmartri @jiparis Ready for review. Fixes the merge commit SHA bug in GitHub Actions PR workflows (#3064). The hash is always overridden from the event payload — works with default actions/checkout shallow clones (depth=1). Full commit metadata is resolved best-effort when the object is available locally. All commits GPG-signed.

@migmartri migmartri requested a review from a team April 22, 2026 13:42
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.

bug: attestation git.head commit SHA is reporting the merge commit SHA instead of the actual commit

1 participant