Skip to content

fix: prefer GitHub PR payload base SHA - #2285

Closed
Erik Osterman (Cloud Posse) (osterman) wants to merge 7 commits into
mainfrom
osterman/gha-pr-base-sha
Closed

Erik Osterman (Cloud Posse) (osterman) wants to merge 7 commits into
mainfrom
osterman/gha-pr-base-sha

Conversation

@osterman

@osterman Erik Osterman (Cloud Posse) (osterman) commented Apr 4, 2026 •

Copy link
Copy Markdown
Member

what

  • prefer pull_request.base.sha from the GitHub Actions event payload when resolving the base for atmos describe affected
  • keep merge-base, HEAD~1, and ref-based resolution as fallbacks, and surface clearer source details when local refs are unavailable
  • add regression tests for payload-SHA, merge-base, closed-PR fallback, and CI auto-detection, and update describe affected docs to describe the new GitHub Actions precedence

why

  • PR workflows running inside GitHub Actions job containers may not have usable local origin/<base> refs even with actions/checkout and fetch-depth: 0
  • using the CI-native payload SHA removes the need for workflow escape hatches like --clone-target-ref=true or explicit git fetch just to compute affected stacks
  • this keeps the default PR CI flow simple while preserving existing push and local behavior

references

  • harden atmos describe affected for GitHub Actions PR job-container workflows

Summary by CodeRabbit

  • New Features

    • CLI forwards PR target-branch info to checkout flows; performs targeted Git fetch and ensures workspace safety before git ops; upload failures now return clearer, actionable hints for auth/permissions.
  • Bug Fixes

    • More deterministic GitHub PR base-commit resolution with refined precedence (event payload → merge-base → parent commit → remote ref).
    • Branch-name validation and clearer fetch error messages.
  • Documentation

    • Updated CI base-commit detection precedence and behavior.
  • Tests

    • Added tests for PR-base fallbacks, fetch behavior, and workspace safety.

@coderabbitai

coderabbitai Bot commented Apr 4, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Prefer PR payload pull_request.base.sha for GitHub Actions base resolution; fall back to merge-base, attempt safe-directory + fetch of the PR target branch and retry, use parent commit for closed PRs, or finally fall back to remote target ref. Thread TargetBranch through describe-affected checkout path; add git helpers/tests and improved upload error hints.

Changes

Cohort / File(s) Summary
GitHub CI base resolution
pkg/ci/providers/github/base.go, pkg/ci/providers/github/base_test.go
Prefer pull_request.base.sha; capture merge-base errors; call EnsureSafeDirectory() before git ops; attempt FetchRef() and retry merge-base; fallback order includes parent-commit for closed PRs and remote ref; populate BaseResolution.TargetBranch; add tests for these flows.
BaseResolution type
pkg/ci/internal/provider/types.go
Added TargetBranch string to BaseResolution.
Git helpers & tests
pkg/git/fetch.go, pkg/git/safe_directory.go, pkg/git/fetch_test.go, pkg/git/safe_directory_test.go
Add FetchRef(repoDir, branch) with branch-name validation and narrow refspec fetch; add EnsureSafeDirectory() to configure Git safe.directory for CI workspaces; add unit/integration tests.
Describe-affected call chain
internal/exec/describe_affected_helpers.go, internal/exec/describe_affected.go, internal/exec/atlantis_generate_repo_config.go, internal/exec/terraform_affected.go, internal/exec/terraform_affected_graph.go, pkg/list/list_affected.go, internal/exec/describe_affected_test.go, internal/exec/describe_affected_helpers_test*
Add TargetBranch field and thread it into ExecuteDescribeAffectedWithTargetRefCheckout (signature changed); on worktree-create failure attempt FetchRef(targetBranch) and retry; update call sites and tests to pass new parameter.
Upload error handling
internal/exec/describe_affected.go
Detect Atmos Pro 403 upload errors and return ErrFailedToUploadStacks with actionable hints about GitHub Actions OIDC/id-token and Atmos Pro permissions.
Docs
website/docs/cli/commands/describe/describe-affected.mdx
Document new PR base-resolution precedence and clarify applicability to pull_request and pull_request_target events.
Tests / Mocks updated
pkg/ci/providers/..._test.go, internal/exec/terraform_affected_test.go, pkg/git/*_test.go
Extend/add tests for new control flows; capture/restore injected resolver funcs; update mock signatures to include new params (targetBranch, authManager).

Sequence Diagram(s)

sequenceDiagram
  participant GH as GitHub Actions (event)
  participant Resolver as ResolveBase
  participant Git as Local Git
  participant Remote as origin

  GH->>Resolver: deliver event payload (pull_request / pull_request_target)
  Resolver->>Resolver: extract pull_request.base.sha
  alt payload.base.sha present
    Resolver-->>GH: return BaseResolution{SHA: payload.base.sha, TargetBranch: base}
  else payload.base.sha missing
    Resolver->>Git: merge-base(HEAD, origin/<base>)
    alt merge-base succeeds
      Resolver-->>GH: return BaseResolution{SHA: merge-base, TargetBranch: base}
    else merge-base fails
      Resolver->>Resolver: record merge-base error
      Resolver->>Git: EnsureSafeDirectory()
      Resolver->>Remote: FetchRef(repoDir, <base>)
      Remote-->>Resolver: fetch result
      alt fetch succeeded
        Resolver->>Git: retry merge-base(HEAD, origin/<base>)
        alt retry succeeds
          Resolver-->>GH: return BaseResolution{SHA: merge-base, TargetBranch: base}
        else
          alt action == "closed"
            Resolver->>Git: parentCommitResolver (HEAD~1)
            Resolver-->>GH: return BaseResolution{SHA: HEAD~1, TargetBranch: base}
          else
            Resolver-->>GH: return BaseResolution{Ref: refs/remotes/origin/<base>, TargetBranch: base}
          end
        end
      else fetch failed
        Resolver-->>GH: return BaseResolution{Ref: refs/remotes/origin/<base>, TargetBranch: base}
      end
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • aknysh
  • milldr
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix: prefer GitHub PR payload base SHA' directly describes the main objective: preferring the payload base SHA for GitHub PR base resolution.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch osterman/gha-pr-base-sha

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 and usage tips.

@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.

🧹 Nitpick comments (1)
pkg/ci/providers/github/base.go (1)

102-106: Keep Source stable by not embedding raw error text.

Appending mergeBaseErr.Error() to Source can make output noisy and environment-specific. Prefer a stable label and keep detailed error text in logs.

Suggested tweak.
  if baseSHA != "" {
  	source := sourcePRBaseSHA
  	if mergeBaseErr != nil {
- 		source += " (merge-base unavailable: " + mergeBaseErr.Error() + ")"
+ 		source += " (merge-base unavailable)"
  	}
  	return &provider.BaseResolution{
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/ci/providers/github/base.go` around lines 102 - 106, The code appends
mergeBaseErr.Error() into the computed Source (when baseSHA != "" using
sourcePRBaseSHA), making Source unstable; instead leave Source stable by
appending a fixed label like " (merge-base unavailable)" or similar, and send
the full mergeBaseErr to the component logger (e.g., log the error via the
existing logger used in this package) so detailed error text is preserved in
logs but not embedded in Source. Update the block building Source (referencing
baseSHA, sourcePRBaseSHA, mergeBaseErr, and Source) to use a stable label and
add a separate logging call that records mergeBaseErr.Error().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/ci/providers/github/base.go`:
- Around line 102-106: The code appends mergeBaseErr.Error() into the computed
Source (when baseSHA != "" using sourcePRBaseSHA), making Source unstable;
instead leave Source stable by appending a fixed label like " (merge-base
unavailable)" or similar, and send the full mergeBaseErr to the component logger
(e.g., log the error via the existing logger used in this package) so detailed
error text is preserved in logs but not embedded in Source. Update the block
building Source (referencing baseSHA, sourcePRBaseSHA, mergeBaseErr, and Source)
to use a stable label and add a separate logging call that records
mergeBaseErr.Error().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9d261a7b-2330-40fc-af10-ac50e778b87a

📥 Commits

Reviewing files that changed from the base of the PR and between b51717f and e601de3.

📒 Files selected for processing (4)
  • internal/exec/describe_affected_test.go
  • pkg/ci/providers/github/base.go
  • pkg/ci/providers/github/base_test.go
  • website/docs/cli/commands/describe/describe-affected.mdx

coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 4, 2026
@mergify

mergify Bot commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

💥 This pull request now has conflicts. Could you fix it Erik Osterman (Cloud Posse) (@osterman)? 🙏

@mergify mergify Bot added the conflict This PR has conflicts label Apr 5, 2026
When Atmos Pro returns HTTP 403 during `describe affected --upload`,
wrap the error with the error builder pattern and surface hints about
OIDC permissions and repository configuration.

Also includes CI provider improvements: auto-fetch target branch when
merge-base fails, safe.directory handling, and TargetBranch propagation
through ExecuteDescribeAffectedWithTargetRefCheckout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Branch

Add tests for FetchRef validation, EnsureSafeDirectory, and CI provider
auto-fetch retry logic. Fix terraform affected test mocks to match
updated ExecuteDescribeAffectedWithTargetRefCheckout signature.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/exec/describe_affected_helpers.go`:
- Around line 234-241: The fetch failure is being swallowed: when
g.FetchRef(localRepoInfo.LocalWorktreePath, targetBranch) returns an error
(fetchErr) you only log it at Debug level and return the original CreateWorktree
error (err), losing the real cause; update the branch where fetchErr != nil to
log at Error (including fetchErr) and return a wrapped/combined error that
includes fetchErr (or replace err with fmt.Errorf("fetch failed: %w", fetchErr))
so callers see the actual FetchRef failure; touch the FetchRef/ CreateWorktree
error path around variables fetchErr, err, worktreePath and use error wrapping
to preserve both contexts.

In `@pkg/git/fetch.go`:
- Around line 16-27: The current branch validation using the validBranchName
regexp in FetchRef is too strict/incorrect for Git rules; remove or bypass the
regexp check and instead validate the branch by invoking git check-ref-format
--branch inside FetchRef (e.g., run exec.CommandContext with repoDir as working
dir or use absolute git call) and treat a non-zero exit as ErrInvalidBranchName;
ensure you preserve the existing error wrapping (return fmt.Errorf("%w: %q",
ErrInvalidBranchName, branch)) and keep the perf.Track defer, and handle command
errors and context/timeout appropriately so only branches accepted by Git pass
validation.

In `@pkg/git/safe_directory.go`:
- Around line 35-36: The current return uses a raw dynamic error
(fmt.Errorf("setting safe.directory for %s: %w\n%s", workspace, err,
string(output))) — replace this with a wrapped, static error from
errors/errors.go (e.g., the git/config related static error) so failures are
classifiable; keep workspace and output as string context by building a
contextual fmt.Errorf for the message and then combine or wrap the underlying
err with the static error using errors.Join or %w as appropriate (update the
code in the safe_directory helper where that fmt.Errorf is used to return
errors.Join(staticGitConfigError, fmt.Errorf("setting safe.directory for %s: %w
-- output: %s", workspace, err, string(output)))).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dad45f93-975b-446e-8b33-bb93bfd2fa66

📥 Commits

Reviewing files that changed from the base of the PR and between e601de3 and 6e0d54c.

📒 Files selected for processing (11)
  • internal/exec/atlantis_generate_repo_config.go
  • internal/exec/describe_affected.go
  • internal/exec/describe_affected_helpers.go
  • internal/exec/describe_affected_test.go
  • internal/exec/terraform_affected.go
  • internal/exec/terraform_affected_graph.go
  • pkg/ci/internal/provider/types.go
  • pkg/ci/providers/github/base.go
  • pkg/git/fetch.go
  • pkg/git/safe_directory.go
  • pkg/list/list_affected.go
✅ Files skipped from review due to trivial changes (1)
  • pkg/ci/internal/provider/types.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/ci/providers/github/base.go
  • internal/exec/describe_affected_test.go

Comment thread internal/exec/describe_affected_helpers.go
Comment thread pkg/git/fetch.go Outdated
Comment thread pkg/git/safe_directory.go Outdated

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/exec/terraform_affected_test.go (1)

829-841: ⚠️ Potential issue | 🔴 Critical

Fix benchmark mock signature to match production function.

The benchmark mock at lines 829–841 uses the old 8-parameter signature, but the production function at internal/exec/terraform_utils.go:350 expects 4 parameters: (info *schema.ConfigAndStacksInfo, affectedList []schema.Affected, params *affectedDepOrderParams, args *DescribeAffectedCmdArgs). When gomonkey attempts to patch with a mismatched signature, it panics immediately—the benchmark will fail during execution. Update the mock to accept the struct-based params instead of individual parameters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/exec/terraform_affected_test.go` around lines 829 - 841, The
benchmark mock for executeTerraformAffectedComponentInDepOrder uses the old
8-parameter signature; update the gomonkey.ApplyFunc replacement to match the
real function signature: func(info *schema.ConfigAndStacksInfo, affectedList
[]schema.Affected, params *affectedDepOrderParams, args
*DescribeAffectedCmdArgs) error, and use params to access the previously
separate values (parentComponent, parentStack, dependents) if needed, returning
nil as before so the patched function matches production and avoids the gomonkey
panic.
🧹 Nitpick comments (2)
internal/exec/terraform_affected_test.go (2)

196-204: Same typos in the error test case mock.

Consistent with the earlier mock but same readability issue.

✏️ Suggested fix
-						tttttttargetBranch string,
+						targetBranch string,
						includeSpaceliftAdminStacks bool,
						includeSettings bool,
						stack string,
						processTemplates bool,
						processYamlFunctions bool,
						skip []string,
						excludeLocked bool,
-						ttttttauthManager auth.AuthManager,
+						authManager auth.AuthManager,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/exec/terraform_affected_test.go` around lines 196 - 204, The test
mock function signature contains leftover typos 'tttttttargetBranch' and
'ttttttauthManager' which reduce readability; rename these parameters to
'targetBranch string' and 'authManager auth.AuthManager' in the mock declaration
and update all usages inside the test (the mock function passed into the test
harness and any callers) to use the corrected parameter names so the signature
matches the earlier mock and improves consistency.

107-115: Parameter name typos in mock signature.

The mock correctly matches the production signature by type and order, but the parameter names have extra t characters: tttttttargetBranch and ttttttauthManager. While gomonkey ignores parameter names, this hurts readability.

✏️ Suggested fix
-						tttttttargetBranch string,
+						targetBranch string,
						includeSpaceliftAdminStacks bool,
						includeSettings bool,
						stack string,
						processTemplates bool,
						processYamlFunctions bool,
						skip []string,
						excludeLocked bool,
-						ttttttauthManager auth.AuthManager,
+						authManager auth.AuthManager,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/exec/terraform_affected_test.go` around lines 107 - 115, The mock
signature in internal/exec/terraform_affected_test.go has parameter name
typos—rename the parameters `tttttttargetBranch` to `targetBranch` and
`ttttttauthManager` to `authManager` in the mock function declaration so the
names match the production signature (types/order already correct); update any
references inside the mock body to use the corrected `targetBranch` and
`authManager` identifiers to improve readability (gomonkey will still work since
it ignores names).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/git/fetch_test.go`:
- Around line 54-66: The test hardcodes "master" when switching back from the
new branch which can fail if git's default branch is different; modify the test
to read the repo's default branch and use that instead of the literal "master":
call runGit(t, originDir, "symbolic-ref", "refs/remotes/origin/HEAD") or parse
the output of runGit invoked with "rev-parse", or runGit(t, originDir, "branch",
"--show-current") after initial commit to capture the default branch name into a
variable (e.g., defaultBranch) and then use runGit(t, originDir, "checkout",
defaultBranch) instead of "master"; also add "strings" to the imports as
suggested.

In `@pkg/git/safe_directory_test.go`:
- Around line 17-24: The test TestEnsureSafeDirectory_ConfiguresSafeDirectory
currently only asserts EnsureSafeDirectory() returns no error but doesn't verify
git global config was mutated; update the test to sandbox git's global config by
setting HOME and XDG_CONFIG_HOME (and USERPROFILE on Windows) to a fresh
t.TempDir(), call EnsureSafeDirectory(), then run git config --global --get-all
safe.directory (via exec.Command) and assert the expected tmpDir value is
present; reference EnsureSafeDirectory and the test
TestEnsureSafeDirectory_ConfiguresSafeDirectory when locating where to add the
environment setup and the git config read/assertion.

---

Outside diff comments:
In `@internal/exec/terraform_affected_test.go`:
- Around line 829-841: The benchmark mock for
executeTerraformAffectedComponentInDepOrder uses the old 8-parameter signature;
update the gomonkey.ApplyFunc replacement to match the real function signature:
func(info *schema.ConfigAndStacksInfo, affectedList []schema.Affected, params
*affectedDepOrderParams, args *DescribeAffectedCmdArgs) error, and use params to
access the previously separate values (parentComponent, parentStack, dependents)
if needed, returning nil as before so the patched function matches production
and avoids the gomonkey panic.

---

Nitpick comments:
In `@internal/exec/terraform_affected_test.go`:
- Around line 196-204: The test mock function signature contains leftover typos
'tttttttargetBranch' and 'ttttttauthManager' which reduce readability; rename
these parameters to 'targetBranch string' and 'authManager auth.AuthManager' in
the mock declaration and update all usages inside the test (the mock function
passed into the test harness and any callers) to use the corrected parameter
names so the signature matches the earlier mock and improves consistency.
- Around line 107-115: The mock signature in
internal/exec/terraform_affected_test.go has parameter name typos—rename the
parameters `tttttttargetBranch` to `targetBranch` and `ttttttauthManager` to
`authManager` in the mock function declaration so the names match the production
signature (types/order already correct); update any references inside the mock
body to use the corrected `targetBranch` and `authManager` identifiers to
improve readability (gomonkey will still work since it ignores names).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 304b6528-f048-409f-bea6-d284828296e3

📥 Commits

Reviewing files that changed from the base of the PR and between 6e0d54c and 8f79cb9.

📒 Files selected for processing (5)
  • internal/exec/terraform_affected_test.go
  • pkg/ci/providers/github/base_test.go
  • pkg/git/fetch.go
  • pkg/git/fetch_test.go
  • pkg/git/safe_directory_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/git/fetch.go
  • pkg/ci/providers/github/base_test.go

Comment thread pkg/git/fetch_test.go Outdated
Comment thread pkg/git/safe_directory_test.go
Merge origin/main into osterman/gha-pr-base-sha, combining the PR
payload base SHA with head SHA and CIEventType assertions from main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mergify mergify Bot removed the conflict This PR has conflicts label Apr 5, 2026
@github-actions

github-actions Bot commented Apr 5, 2026 •

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 78c5a38.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/exec/describe_affected.go (1)

331-335: ⚠️ Potential issue | 🟠 Major

Gate the retry on missing-commit/ref errors only, not all CreateWorktree failures.

Currently the retry at lines 234-242 in describe_affected_helpers.go triggers on any CreateWorktree error. Since CreateWorktree wraps all errors (temp-dir creation, git command failures, missing refs) as ErrGitRefNotFound, the retry logic can't distinguish between a genuinely missing ref and unrelated failures like permission or temp-path issues. This causes unnecessary fetch and retry attempts for problems that won't be fixed by fetching.

Check the CreateWorktree error message or output to confirm it's a missing-commit/ref case before attempting the fetch and retry.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/exec/describe_affected.go`:
- Around line 452-465: The non-403 upload error path should also return a
wrapped static error instead of bubbling raw; update the UploadAffectedStacks
error handling so that after detecting and handling the 403 case, any other
uploadErr is returned via
errUtils.Build(errUtils.ErrFailedToUploadStacks).WithCause(uploadErr).Err()
(keeping any relevant hints if desired) instead of returning uploadErr directly;
locate the apiClient.UploadAffectedStacks call and replace the final "return
uploadErr" with the wrapped errUtils.Build(...) return to ensure callers receive
the stable ErrFailedToUploadStacks sentinel.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7fd292d7-ade0-4ccf-938e-ec31f54ec044

📥 Commits

Reviewing files that changed from the base of the PR and between 8f79cb9 and 0e624dd.

📒 Files selected for processing (2)
  • internal/exec/describe_affected.go
  • internal/exec/describe_affected_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/exec/describe_affected_test.go

Comment thread internal/exec/describe_affected.go Outdated
…, and tests

- Join fetch error with worktree error so failures propagate to callers
- Wrap non-403 upload errors with ErrFailedToUploadStacks sentinel
- Replace regex branch validation with git check-ref-format for correctness
- Wrap safe.directory error in static ErrGitCommandFailed error
- Detect default branch dynamically in tests instead of hardcoding "master"
- Sandbox git global config in safe_directory test and assert configured value

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/git/fetch.go`:
- Around line 15-22: Change isValidBranchName to return (bool, error) and update
its callers (e.g., FetchRef) to handle and propagate real git execution errors:
run cmd.Run(), if err==nil return (true, nil); if err is an *exec.ExitError
return (false, nil) to indicate an invalid branch name; otherwise return (false,
err) so callers can distinguish git failures (missing binary, permissions, etc.)
from validation rejection and surface the proper error up the stack.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e4b069a7-2070-449e-b16d-1aac164c9f2d

📥 Commits

Reviewing files that changed from the base of the PR and between 0e624dd and f4e9161.

📒 Files selected for processing (6)
  • internal/exec/describe_affected.go
  • internal/exec/describe_affected_helpers.go
  • pkg/git/fetch.go
  • pkg/git/fetch_test.go
  • pkg/git/safe_directory.go
  • pkg/git/safe_directory_test.go
✅ Files skipped from review due to trivial changes (1)
  • pkg/git/fetch_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/git/safe_directory_test.go
  • pkg/git/safe_directory.go
  • internal/exec/describe_affected_helpers.go

Comment thread pkg/git/fetch.go Outdated
…validateBranchName

Rename isValidBranchName to validateBranchName, returning error instead of bool.
Now distinguishes *exec.ExitError (invalid branch name) from other errors
(missing git binary, permissions) so callers see the actual failure cause.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codecov

codecov Bot commented Apr 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.40000% with 32 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.03%. Comparing base (9d4a2d8) to head (78c5a38).
⚠️ Report is 33 commits behind head on main.

Files with missing lines Patch % Lines
pkg/ci/providers/github/base.go 85.50% 8 Missing and 2 partials ⚠️
internal/exec/describe_affected.go 18.18% 9 Missing ⚠️
internal/exec/describe_affected_helpers.go 0.00% 7 Missing ⚠️
pkg/git/safe_directory.go 83.33% 1 Missing and 1 partial ⚠️
internal/exec/atlantis_generate_repo_config.go 0.00% 1 Missing ⚠️
internal/exec/terraform_affected_graph.go 0.00% 1 Missing ⚠️
pkg/git/fetch.go 95.45% 1 Missing ⚠️
pkg/list/list_affected.go 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2285      +/-   ##
==========================================
+ Coverage   76.99%   77.03%   +0.04%     
==========================================
  Files        1059     1061       +2     
  Lines      100601   100711     +110     
==========================================
+ Hits        77453    77579     +126     
+ Misses      18852    18839      -13     
+ Partials     4296     4293       -3     
Flag Coverage Δ
unittests 77.03% <74.40%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
internal/exec/terraform_affected.go 92.00% <100.00%> (+0.08%) ⬆️
internal/exec/atlantis_generate_repo_config.go 59.39% <0.00%> (-0.19%) ⬇️
internal/exec/terraform_affected_graph.go 50.69% <0.00%> (-0.36%) ⬇️
pkg/git/fetch.go 95.45% <95.45%> (ø)
pkg/list/list_affected.go 39.68% <0.00%> (-0.22%) ⬇️
pkg/git/safe_directory.go 83.33% <83.33%> (ø)
internal/exec/describe_affected_helpers.go 39.39% <0.00%> (-1.75%) ⬇️
internal/exec/describe_affected.go 75.59% <18.18%> (-1.96%) ⬇️
pkg/ci/providers/github/base.go 84.97% <85.50%> (+6.71%) ⬆️

... and 6 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mergify

mergify Bot commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

💥 This pull request now has conflicts. Could you fix it Erik Osterman (Cloud Posse) (@osterman)? 🙏

@mergify mergify Bot added the conflict This PR has conflicts label Apr 12, 2026
@osterman

Copy link
Copy Markdown
Member Author

Closing in favor of #2380, which keeps git merge-base as the primary base-resolution strategy and uses event.pull_request.base.sha only as a fallback when merge-base cannot recover.

Why supersede rather than land this PR as-is:

  • The PRD explicitly flags pull_request.base.sha as stale (docs/prd/native-ci/framework/base-resolution.md): it is frozen at the last PR sync event, so promoting it to the primary strategy would still produce false positives when other PRs land on the target branch between the last sync and the workflow run.
  • git merge-base(HEAD, origin/<target>) is correct in every scenario regardless of how out of date the PR is. The only blocker for it in CI was shallow checkouts not having origin/<target> available.
  • fix(describe-affected): resolve PR base via merge-base with shallow-clone self-heal #2380 makes merge-base self-heal: when the target ref isn't local, it runs a targeted git fetch origin <target> (and one bounded --deepen=200) and retries. The fetch helpers (pkg/git/fetch.go) and the targetBranch plumbing in ExecuteDescribeAffectedWithTargetRefCheckout are lifted from this PR — credit retained in the commit and the supersession note in docs/fixes/2026-04-30-describe-affected-out-of-date-pr.md.

The customer-reported regression is fixed in #2380 with the same shallow-checkout coverage you had here, plus a stronger test bar (the new TestResolveBaseFromCI requires describe.SHA populated and describe.Ref empty, so any future regression to the buggy ref-tip path fails the unit tests).

The improved upload-error hints from this PR are unrelated to base resolution — happy to land them in a small standalone PR if you want.

Thanks for the original work — it pointed straight at the right code paths.

Andriy Knysh (aknysh) added a commit that referenced this pull request May 1, 2026
…lone self-heal (#2380)

* fix(describe-affected): resolve PR base via merge-base with shallow-clone self-heal; drop buggy origin-tip fallback

When `ci.enabled: true` and the PR was not up to date with the target
branch, `atmos describe affected` could report many more affected
components than the PR actually modified. Root cause: when
`MergeBase(HEAD, origin/<target>)` failed (shallow CI checkouts where
`origin/<target>` is not fetched), the GitHub provider fell through to
returning the *ref* `refs/remotes/origin/<target>`, which downstream
resolved to the *current tip* of the target branch. The resulting
tree-to-tree diff included every commit on `<target>` that the PR
hadn't pulled in.

Fix:

- `pkg/git/merge_base.go` adds `MergeBaseWithAutoFetch` that
  transparently runs `git fetch origin <target>` (and optionally one
  `--deepen=200` fetch) when `MergeBase` cannot resolve a fork point.
  Bounded — at most one fetch and one deepen per call.
- `pkg/ci/providers/github/base.go` uses `MergeBaseWithAutoFetch` and
  changes the post-fallback chain. The new last-resort is
  `event.pull_request.base.sha` (frozen at last PR sync, never the
  current tip). The legacy `refs/remotes/origin/<target>` ref path is
  kept only for hand-crafted payloads with no `base.sha`, with a
  `log.Warn` explaining it may include unrelated commits.
- `ExecuteDescribeAffectedWithTargetRefCheckout` accepts a new
  `targetBranch` parameter; on worktree creation failure it runs
  `git fetch origin <targetBranch>` and retries once. Threaded
  through `DescribeAffectedCmdArgs` and all callers.
- `BaseResolution` gains a `TargetBranch` field so providers can
  surface the target branch alongside the resolved base.
- Adds `pkg/git/fetch.go` (`FetchRef`, `DeepenFetch`) lifted from
  the open PR #2285 (which this PR supersedes — see the docs/fixes
  entry).

Tests:

- `pkg/git/merge_base_test.go` — new
  `TestMergeBaseWithAutoFetch_RecoversFromMissingRef` builds an
  origin/clone pair, deletes `origin/main` to simulate the shallow
  case, and asserts the recovered SHA is the fork point. Also
  covers `ErrHeadOnTargetBranch` propagation and the no-recovery
  path. Existing `MergeBase` tests migrated to `t.Chdir`.
- `pkg/ci/providers/github/base_test.go` — adds
  `TestResolveBase_PullRequest_OutOfDate_FallsBackToPayloadSHA`
  that reproduces the customer scenario at unit-test level and
  asserts we end up with a SHA, not the buggy origin-tip ref.
- `internal/exec/describe_affected_test.go:TestResolveBaseFromCI`
  hardened to require `describe.SHA` is populated and
  `describe.Ref` empty — guards against any future regression.

Docs:

- `docs/fixes/2026-04-30-describe-affected-out-of-date-pr.md`
  documents the issue, root cause, fix, and the rejected
  `pull_request.merge_commit_sha` alternative the user suggested.
- PRD `docs/prd/native-ci/framework/base-resolution.md`: removed
  "handles this gracefully" language; updated resolution matrix.
- Blog post and command docs updated to reflect that merge-base
  is the actual primary strategy (the previous text predated
  PR #2241's merge-base implementation).

Refs: #2241, supersedes #2285.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(describe-affected): trust GitHub Actions workspace before git ops

Container jobs in GitHub Actions run as a different user than the
checkout owner. Without `safe.directory` set, every git command
(including the auto-fetch we just added in MergeBaseWithAutoFetch
and the worktree-creation retry) fails with "fatal: detected dubious
ownership in repository at '/github/workspace'".

Call git.EnsureGitSafeDirectory() at the top of ResolveBase() so all
git operations the GitHub provider triggers (merge-base, fetch,
parent-commit lookup) succeed in container runners. The function is
a no-op outside GitHub Actions so it's safe to call unconditionally.

Also: hardened the two new fallback tests to use a non-existent
target branch name. They previously assumed merge-base would fail in
the test environment, which broke when running locally inside a real
clone of the atmos repo (the host repo *has* a `main` branch and
merge-base succeeded). Using `nonexistent-target-for-pr2380` makes
the assertions deterministic regardless of host state.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(describe-affected): only call EnsureGitSafeDirectory when ci.enabled is true

EnsureGitSafeDirectory mutates the runner's global git config (adds
GITHUB_WORKSPACE to safe.directory). It should only run when the user
has explicitly opted into CI auto-detect via ci.enabled: true.

Move the call from pkg/ci/providers/github/base.go:ResolveBase()
(unconditional) to internal/exec/describe_affected.go:resolveBaseFromCI()
(only invoked when ci.enabled is true). The provider-layer doc-comment
notes that callers are responsible for safe-directory setup.

EnsureGitSafeDirectory itself remains a no-op outside GitHub Actions,
so the practical effect is: with this commit, Atmos only writes to
~/.gitconfig when both ci.enabled is true AND we're running inside
GitHub Actions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(git): wrap fetch/deepen errors with static sentinels; fix doc fallback chain

Address PR review feedback:
- Add ErrFetchOrigin / ErrDeepenOrigin sentinels in errors/errors.go.
- Wrap pkg/git/fetch.go FetchRef and DeepenFetch errors with the new
  sentinels (double %w preserves both sentinel and underlying err for
  errors.Is checks).
- Update TestFetchRef_NonexistentBranch to assert errors.Is(err,
  ErrFetchOrigin) instead of matching the old message string.
- Fix base-resolution PRD Validation bullets to match the documented
  fallback chain (MergeBaseWithAutoFetch -> base.sha -> ref-with-warn).
- Add `text` language hint to fenced diagram block in fixes doc to
  satisfy markdownlint MD040.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(git): cover DeepenFetch failure path with ErrDeepenOrigin sentinel

Mirrors TestFetchRef_NonexistentBranch: clones a temp origin and asks
DeepenFetch to fetch a branch that does not exist there, asserting the
error chain wraps errUtils.ErrDeepenOrigin so a regression in the
sentinel wrapping cannot slip through.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(git): cover unshallow and deepen branches in fetch/merge_base

Bring pkg/git coverage from 75.x% to 80.3% by exercising two previously
untested paths:

- TestDeepenFetch_Unshallow: shallow-clones a multi-commit origin (depth=1),
  then calls DeepenFetch(repo, branch, 0) to take the depth<=0 --unshallow
  branch and asserts the .git/shallow marker is removed afterwards. Brings
  DeepenFetch from 80% -> 100% line coverage.

- TestMergeBaseWithAutoFetch_DeepenPathExhausted: uses an orphan branch
  to deterministically produce ErrNoCommonAncestor (rather than the
  shallow-clone "object not found" path, which the auto-fetch logic does
  not classify as deepen-recoverable). Exercises the deepen branch end
  to end and asserts the function propagates the error when deepen
  cannot recover. Brings MergeBaseWithAutoFetch from 53.6% -> 71.4%.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(git): MergeBase honors repoDir; tighten test sentinels and Windows file URI

Address PR review feedback:

- MergeBase now takes (repoDir, targetBranch) and opens the repo at
  repoDir directly via go-git PlainOpenWithOptions, instead of relying
  on the cwd-scoped GetLocalRepo() helper. MergeBaseWithAutoFetch
  threads its repoDir argument into all three MergeBase calls so the
  initial computation, post-fetch retry, and post-deepen retry all
  operate on the same repository. Previously MergeBaseWithAutoFetch's
  repoDir parameter was used only for FetchRef/DeepenFetch, leaving
  the merge-base steps silently dependent on cwd.

- Tests no longer t.Chdir into the test repo before exercising
  MergeBase / MergeBaseWithAutoFetch — the explicit repoDir argument
  is sufficient and removing chdir actually verifies the new contract.

- Tighten error assertions:
  * TestMergeBase_ErrorWhenTargetRefMissing: errors.Is plumbing.ErrReferenceNotFound
  * TestMergeBaseWithAutoFetch_DeepenPathExhausted: errors.Is ErrNoCommonAncestor
  * TestMergeBaseWithAutoFetch_ReturnsErrorWhenFetchImpossible: errors.Is plumbing.ErrReferenceNotFound

- Windows-safe file URI in TestDeepenFetch_Unshallow — build with
  net/url + filepath.ToSlash instead of "file://"+originDir, which
  is malformed on drive-letter paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(git): extract worktree-with-fetch-recovery helper, cover with tests

The shallow-clone self-heal logic in
ExecuteDescribeAffectedWithTargetRefCheckout (8 lines) was hard to
unit-test because it lived inside a 350-line orchestrator that the
existing tests stub via gomonkey. Hoist it into a small package-level
helper in pkg/git so it can be exercised end-to-end with real git
operations.

- New CreateWorktreeWithFetchRecovery(repoDir, targetCommit,
  targetBranch) in pkg/git/worktree.go: tries CreateWorktree, and if
  that fails AND a non-empty targetBranch is supplied, performs a
  one-shot FetchRef(repoDir, targetBranch) and retries. On final
  failure, joins the original CreateWorktree error with the fetch
  error so callers retain hints about both failures.

- describe_affected_helpers.go now calls this helper directly,
  shrinking the inline self-heal block from 8 lines to 1.

- Three tests cover the three branches at 100% line coverage:
  * SuccessNoFetchNeeded — first CreateWorktree succeeds; fetch
    must NOT run even when targetBranch is non-empty.
  * SuccessAfterFetch — clone tracks an older origin/main; a new
    commit lands on origin after the clone; missing SHA becomes
    available after FetchRef and the retry succeeds. (This is the
    customer-reported PR base.sha scenario.)
  * FailsWhenFetchFails — bogus SHA + non-existent target branch;
    asserts the joined error chain is reachable via errors.Is on
    ErrFetchOrigin.

Net effect: pkg/git coverage 80.3% -> 81.1%, and the
describe_affected_helpers self-heal lines that were 0% covered are
gone (replaced with one delegation line).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(git): gate worktree self-heal to ErrGitRefNotFound; pin exec contracts in tests

Address PR review feedback:

- CreateWorktreeWithFetchRecovery now gates the fetch/retry path on
  errors.Is(err, errUtils.ErrGitRefNotFound). Previously *any*
  CreateWorktree failure with a non-empty targetBranch would log
  "Target commit not available locally, fetching base branch" and
  attempt a fetch — including unrelated infrastructure failures
  (temp-dir creation, permission denied, repo state corruption). Those
  errors now propagate directly.

- Negative-path test TestCreateWorktreeWithFetchRecovery_GateSkipsNonRefNotFoundError
  forces os.MkdirTemp to fail (TMPDIR/TEMP/TMP point at a nonexistent
  path) and asserts the gate skips the recovery path: neither
  ErrFetchOrigin nor ErrGitRefNotFound appear in the returned chain.
  Side-benefit: this test exercises the MkdirTemp failure branch in
  CreateWorktree itself, bringing it from 92.3% to 100%.

- TestFetchRef_NonexistentBranch and TestDeepenFetch_NonexistentBranch
  now also assert errors.As(&exec.ExitError{}) and a non-zero exit
  code, pinning the contract that the wrapped error chain preserves
  the underlying shell exit-status info. (FetchRef/DeepenFetch use
  raw exec.Command rather than the workflow ShellRunner that produces
  errUtils.ExitCodeError, so *exec.ExitError is the equivalent
  contract here.)

Coverage: pkg/git 81.1% -> 81.6%; CreateWorktree 92.3% -> 100%;
CreateWorktreeWithFetchRecovery stays at 100%.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [autocommit] formatting fixes

* fix(git): narrow CreateWorktree ref-not-found classification

Previously CreateWorktree blanket-tagged every git worktree add failure
as ErrGitRefNotFound, causing CreateWorktreeWithFetchRecovery's gate to
fire its fetch retry on unrelated failures (path conflicts, repo state
corruption, permissions) and to surface a misleading "make sure the ref
is correct" hint for non-ref problems.

Add ErrGitWorktreeAdd sentinel for generic worktree-add failures and
parse git stderr to reserve ErrGitRefNotFound for true missing-ref
cases. The recovery gate stays on ErrGitRefNotFound, which now fires
only when fetch is actually a candidate fix. Test coverage includes
the new non-ref classification and a parallel gate-skip case through
the recovery helper. Also pins both joined sentinels (ErrFetchOrigin
and ErrGitRefNotFound) in the fetch-failure recovery test so the
preserved hint context can't regress.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: atmos-pro[bot] <atmos-pro[bot]@users.noreply.github.com>
Co-authored-by: Andriy Knysh <aknysh@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
preview — 78c5a389 Deployed Apr 7, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch A minor, backward compatible change size/m Medium size PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant