Skip to content

fix: Colima and non-Docker-Desktop runtime compatibility - #35

Open
schuyler wants to merge 6 commits into
obra:mainfrom
schuyler:fix/colima-compat
Open

schuyler wants to merge 6 commits into
obra:mainfrom
schuyler:fix/colima-compat

Conversation

@schuyler

@schuyler schuyler commented Mar 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Make containers work with Colima, Podman, and other non-Docker-Desktop runtimes
  • Use $HOME for E2E test temp dirs so they're mountable by all runtimes (Colima only shares /Users by default, not /var/folders)
  • Run usermod/groupmod/chown as root in updateRemoteUserUID (non-Docker-Desktop runtimes don't implicitly run exec as root)
  • Mount ~/.config/gh on all platforms, not just Linux
  • Set GIT_CONFIG_COUNT env vars to configure safe.directory=* and prevent "dubious ownership" errors
  • Remove macOS gate from updateRemoteUserUID since Docker Desktop isn't the only macOS runtime
  • Fix staticcheck QF1012 lint violation in dockerfile generator

Test plan

  • go test ./pkg/runner/ -run TestE2E_UpdateRemoteUserUID -v
  • go test ./pkg/runner/ -run TestE2E_GitSafeDirectory -v
  • go test ./pkg/runner/ -run TestE2E_GHCredsMount -v
  • Full E2E suite on macOS with Colima (all pass except pre-existing Docker Compose plugin failures)
  • CI tests pass
  • Verify E2E tests pass on macOS with Docker Desktop

Summary by CodeRabbit

  • New Features

    • GH CLI credentials now mount across platforms; git safe.directory is auto-configured for container sessions.
    • UID/GID synchronization now runs cross-platform to ensure correct ownership inside containers.
  • Bug Fixes

    • Git operations in containers with mismatched ownership now work reliably.
  • Documentation

    • Clarified GitHub CLI credential wording, simplified macOS Keychain notes, and updated macOS Docker runtime guidance.
  • Tests

    • Added end-to-end tests for safe.directory handling and GH credential mounts.

schuyler added 3 commits March 2, 2026 17:45
…ntimes

The codebase assumed macOS always means Docker Desktop, which provides
transparent UID mapping and other special behaviors. Colima, Podman, and
OrbStack on macOS don't provide these, causing several failures.

Three fixes, all applied unconditionally (harmless no-ops where not needed):

1. Configure git safe.directory via GIT_CONFIG_COUNT env vars to prevent
   "dubious ownership" errors when container user UID differs from
   mounted workspace file ownership. Uses wildcard (*) matching VS Code
   devcontainer behavior.

2. Mount ~/.config/gh on all platforms. Previously gated by isLinux,
   making --gh-creds a silent no-op on macOS. The comment referencing
   Keychain-based copying was stale — that code never existed.

3. Run UID/GID sync on all platforms. Previously skipped on macOS with
   the assumption that Docker Desktop handles it automatically. The
   function already no-ops when UIDs match, so this is safe everywhere.
…times

Colima and Podman only share /Users by default. os.MkdirTemp("", ...)
creates dirs under /var/folders which macOS resolves to /private/var/folders
via symlink — a path the container VM can't access.

Fixes TestE2E_RealWorldNodeJS, TestE2E_CustomMounts,
TestE2E_MountVariableSubstitution, TestE2E_InitializeCommand,
and TestE2E_InitializeCommand_Object.
These commands require root privileges but the docker exec calls were
not specifying -u root, causing silent failures on non-Docker-Desktop
runtimes where the container user lacks root access.
@coderabbitai

coderabbitai Bot commented Mar 3, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@schuyler has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 7 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 58a0bbf and fc7ba31.

📒 Files selected for processing (2)
  • internal/dockerfile/dockerfile_generator.go
  • pkg/runner/runner.go
📝 Walkthrough

Walkthrough

Always-mount GitHub CLI config when present, export git safe.directory via environment for containerized git, and perform UID/GID synchronization on all platforms; tests now use home-directory roots and two new E2E tests were added.

Changes

Cohort / File(s) Summary
Documentation
README.md
Rewrote GitHub CLI credential wording to "GitHub CLI authentication"; simplified macOS Keychain notes; expanded Docker runtime options for macOS (Docker Desktop, Colima, Podman).
Test Infrastructure
pkg/runner/e2e_test.go
Switched temp/test roots to home-directory paths for mount visibility; added TestE2E_GitSafeDirectory and TestE2E_GHCredsMount; removed OS-specific gating for UID/GID sync and GH creds tests; added devcontainer.json test fixtures.
Container Runtime / Runner
pkg/runner/runner.go
Unconditionally mount ~/.config/gh when present; inject GIT_CONFIG_COUNT, GIT_CONFIG_KEY_0, GIT_CONFIG_VALUE_0 env vars to configure git safe.directory; run UID/GID synchronization unconditionally and execute groupmod/usermod/chown steps as root (-u root) with fallback behavior for GID updates.
Dockerfile Generator (internal)
internal/dockerfile/dockerfile_generator.go
Replaced sb.WriteString(fmt.Sprintf(...)) with fmt.Fprintf(&sb, ...) across Dockerfile generation codepaths; no functional changes to generated output.

Sequence Diagram

sequenceDiagram
    participant Runner
    participant Host as Host Filesystem
    participant Container
    participant Git as Git (in container)
    participant GHCli as GitHub CLI (in container)

    Runner->>Host: Check for ~/.config/gh
    alt GH config exists
        Runner->>Container: Mount ~/.config/gh (all platforms)
    end

    Runner->>Container: Export GIT_CONFIG_COUNT, GIT_CONFIG_KEY_0, GIT_CONFIG_VALUE_0
    Runner->>Container: Start container with mounts & env

    alt UID/GID mismatch detected
        Runner->>Container: Exec usermod/groupmod/chown as root (-u root)
        Container->>Container: Apply UID/GID and ownership changes
    end

    Git->>Git: Read safe.directory from injected config
    GHCli->>GHCli: Use mounted credentials from host
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰
I hopped from home into a box so neat,
Mounted creds and safe dirs with tiny feet,
UIDs aligned, permissions sang in tune,
Tests now bloom beneath a friendly moon,
A rabbit claps — containers hum, hooray!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.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 title 'fix: Colima and non-Docker-Desktop runtime compatibility' directly reflects the main objective of the changeset, which is to make containers compatible with Colima, Podman, and other non-Docker-Desktop runtimes.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (2)
pkg/runner/runner.go (2)

1835-1841: ⚠️ Potential issue | 🟠 Major

Don’t early-return on UID match without validating GID.

The function returns when UID matches, even if GID is still mismatched. That leaves group ownership drift unresolved and breaks the stated UID/GID sync behavior.

✅ Check both UID and GID before no-op
 	currentUID := strings.TrimSpace(currentUIDStr)
-	if currentUID == fmt.Sprintf("%d", hostUID) {
+	currentGIDStr, err := dockerClient.Run("exec", containerID, "id", "-g", username)
+	if err != nil {
+		return fmt.Errorf("failed to get current GID for user '%s': %w", username, err)
+	}
+	currentGID := strings.TrimSpace(currentGIDStr)
+	if currentUID == fmt.Sprintf("%d", hostUID) && currentGID == fmt.Sprintf("%d", hostGID) {
 		if verbose {
-			fmt.Fprintf(os.Stderr, "User '%s' already has correct UID=%d, skipping sync\n", username, hostUID)
+			fmt.Fprintf(os.Stderr, "User '%s' already has correct UID=%d GID=%d, skipping sync\n", username, hostUID, hostGID)
 		}
 		return nil
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/runner.go` around lines 1835 - 1841, The early return checks only
currentUID and skips syncing even when GID differs; change the no-op to validate
both UID and GID before returning by reading/trim-parsing the current GID
(analogous to currentUID/currentUIDStr) and comparing it to hostGID as well, and
only if currentUID == fmt.Sprintf("%d", hostUID) AND currentGID ==
fmt.Sprintf("%d", hostGID) (use the same verbose logging path with username and
hostUID/hostGID) should the function return nil; otherwise proceed to fix the
mismatched group ownership.

858-863: ⚠️ Potential issue | 🟠 Major

Mount GitHub CLI credentials read-only by default.

This bind mount is writable, which lets container processes modify host ~/.config/gh auth state. For credential mounts, default should be :ro unless explicit write access is required.

🔒 Suggested hardening
-			args = append(args, "-v", fmt.Sprintf("%s:/home/%s/.config/gh", ghConfigPath, devConfig.RemoteUser))
+			args = append(args, "-v", fmt.Sprintf("%s:/home/%s/.config/gh:ro", ghConfigPath, devConfig.RemoteUser))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/runner.go` around lines 858 - 863, The bind mount of the GitHub
CLI config under the config.Credentials.GH check is currently writable; change
the volume string constructed for ghConfigPath so it mounts read-only by default
(add :ro to the target spec in the fmt.Sprintf that appends to args for
devConfig.RemoteUser). If there is a use-case requiring write access, gate that
with an explicit option (e.g., a new flag in config.Credentials or devConfig)
and only omit :ro when that flag is set; keep the fileExists check and args
append logic intact.
🤖 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/runner/e2e_test.go`:
- Around line 3877-3884: The test currently writes a fixed marker filename into
ghConfigDir and may delete real user data via os.Remove or os.RemoveAll; change
it to create an isolated temp config dir (use os.MkdirTemp) and point the test
at that directory (or set XDG_CONFIG_HOME) so markerFile is created inside the
temp dir, write the marker with os.WriteFile as before, and on cleanup only
remove files/dirs the test created (i.e., remove the temp dir unconditionally)
and avoid touching the real ghConfigDir; update logic that references
createdGHDir, markerFile, ghConfigDir and any os.RemoveAll calls to only remove
the temp directory you created.

In `@pkg/runner/runner.go`:
- Around line 1003-1009: The current code appends GIT_CONFIG_VALUE_0="*" which
globally trusts all repos; change the value to the mounted workspace path
instead so only that workspace is marked safe. Update the args append that sets
GIT_CONFIG_VALUE_0 (the line using GIT_CONFIG_VALUE_0=*) to use the workspace
path variable used by the runner (e.g., the workspace or repo path string the
container mounts), ensuring it is properly escaped/quoted; leave
GIT_CONFIG_COUNT and GIT_CONFIG_KEY_0 as-is so the safe.directory entry is added
but scoped to the workspace.

In `@README.md`:
- Line 367: Update the README Requirements section to reflect runtime
compatibility with Colima/Podman instead of or alongside the previous “Docker
Desktop on macOS” wording: locate the Requirements heading and replace the
macOS-specific Docker Desktop line (the text “Docker Desktop on macOS”) with a
statement that lists supported runtimes (e.g., Docker Desktop, Colima, Podman)
and any OS-specific notes; ensure the GitHub CLI path note (`~/.config/gh`)
remains accurate and remove any contradictory phrasing so the Requirements
section matches this PR’s Colima/Podman compatibility goal.

---

Outside diff comments:
In `@pkg/runner/runner.go`:
- Around line 1835-1841: The early return checks only currentUID and skips
syncing even when GID differs; change the no-op to validate both UID and GID
before returning by reading/trim-parsing the current GID (analogous to
currentUID/currentUIDStr) and comparing it to hostGID as well, and only if
currentUID == fmt.Sprintf("%d", hostUID) AND currentGID == fmt.Sprintf("%d",
hostGID) (use the same verbose logging path with username and hostUID/hostGID)
should the function return nil; otherwise proceed to fix the mismatched group
ownership.
- Around line 858-863: The bind mount of the GitHub CLI config under the
config.Credentials.GH check is currently writable; change the volume string
constructed for ghConfigPath so it mounts read-only by default (add :ro to the
target spec in the fmt.Sprintf that appends to args for devConfig.RemoteUser).
If there is a use-case requiring write access, gate that with an explicit option
(e.g., a new flag in config.Credentials or devConfig) and only omit :ro when
that flag is set; keep the fileExists check and args append logic intact.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6edecfe and 37f8a08.

📒 Files selected for processing (3)
  • README.md
  • pkg/runner/e2e_test.go
  • pkg/runner/runner.go

Comment thread pkg/runner/e2e_test.go
Comment on lines +3877 to +3884
markerFile := filepath.Join(ghConfigDir, "packnplay-test-marker")
require.NoError(t, os.WriteFile(markerFile, []byte("gh-creds-test\n"), 0644))
defer os.Remove(markerFile)
defer func() {
if createdGHDir {
os.RemoveAll(ghConfigDir)
}
}()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid mutating/deleting real user GH config content during test cleanup.

The test uses a fixed marker filename in ~/.config/gh and can delete a pre-existing user file with the same name. Cleanup also removes the whole directory when it was created by the test, which can delete files created during the test window by other processes.

🛠️ Safer marker handling
-	markerFile := filepath.Join(ghConfigDir, "packnplay-test-marker")
+	markerFile := filepath.Join(ghConfigDir, fmt.Sprintf("packnplay-test-marker-%d", time.Now().UnixNano()))
 	require.NoError(t, os.WriteFile(markerFile, []byte("gh-creds-test\n"), 0644))
 	defer os.Remove(markerFile)
 	defer func() {
 		if createdGHDir {
-			os.RemoveAll(ghConfigDir)
+			// Remove only if still empty; avoid deleting user-created files.
+			_ = os.Remove(ghConfigDir)
 		}
 	}()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
markerFile := filepath.Join(ghConfigDir, "packnplay-test-marker")
require.NoError(t, os.WriteFile(markerFile, []byte("gh-creds-test\n"), 0644))
defer os.Remove(markerFile)
defer func() {
if createdGHDir {
os.RemoveAll(ghConfigDir)
}
}()
markerFile := filepath.Join(ghConfigDir, fmt.Sprintf("packnplay-test-marker-%d", time.Now().UnixNano()))
require.NoError(t, os.WriteFile(markerFile, []byte("gh-creds-test\n"), 0644))
defer os.Remove(markerFile)
defer func() {
if createdGHDir {
// Remove only if still empty; avoid deleting user-created files.
_ = os.Remove(ghConfigDir)
}
}()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/e2e_test.go` around lines 3877 - 3884, The test currently writes a
fixed marker filename into ghConfigDir and may delete real user data via
os.Remove or os.RemoveAll; change it to create an isolated temp config dir (use
os.MkdirTemp) and point the test at that directory (or set XDG_CONFIG_HOME) so
markerFile is created inside the temp dir, write the marker with os.WriteFile as
before, and on cleanup only remove files/dirs the test created (i.e., remove the
temp dir unconditionally) and avoid touching the real ghConfigDir; update logic
that references createdGHDir, markerFile, ghConfigDir and any os.RemoveAll calls
to only remove the temp directory you created.

Comment thread pkg/runner/runner.go
Comment on lines +1003 to +1009
// Configure git safe.directory to prevent "dubious ownership" errors
// when container user UID differs from mounted workspace file ownership
// (common with Colima, Podman, and other non-Docker-Desktop runtimes).
// Uses GIT_CONFIG_COUNT mechanism, additive to the mounted read-only .gitconfig.
args = append(args, "-e", "GIT_CONFIG_COUNT=1")
args = append(args, "-e", "GIT_CONFIG_KEY_0=safe.directory")
args = append(args, "-e", "GIT_CONFIG_VALUE_0=*")

@coderabbitai coderabbitai Bot Mar 3, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check the exact content of the file at the specified lines
cat -n pkg/runner/runner.go | sed -n '1000,1020p'

Repository: obra/packnplay

Length of output: 1145


🏁 Script executed:

# Find the workingDir variable definition in the file
rg 'workingDir' pkg/runner/runner.go -B 2 -A 2

Repository: obra/packnplay

Length of output: 2633


🏁 Script executed:

# Get more context around lines 1003-1009
cat -n pkg/runner/runner.go | sed -n '980,1030p'

Repository: obra/packnplay

Length of output: 2591


🌐 Web query:

Git safe.directory GIT_CONFIG_COUNT GIT_CONFIG_KEY configuration environment variables

💡 Result:

GIT_CONFIG_COUNT / GIT_CONFIG_KEY_<n> / GIT_CONFIG_VALUE_<n> let you inject Git config via environment variables (a “command” scope config source). If GIT_CONFIG_COUNT is a positive integer, Git reads zero-indexed pairs GIT_CONFIG_KEY_0+GIT_CONFIG_VALUE_0 … up to COUNT-1. Missing keys/values are errors. These env-provided values override config files, but are overridden by explicit git -c key=value options. [1]

Example (set multiple runtime config entries):

export GIT_CONFIG_COUNT=2
export GIT_CONFIG_KEY_0=safe.directory
export GIT_CONFIG_VALUE_0=/srv/shared/repo
export GIT_CONFIG_KEY_1=core.sshCommand
export GIT_CONFIG_VALUE_1="ssh -i /run/secrets/deploy_key"
git status

(“command” scope includes these env vars and -c.) [2]

safe.directory specifics

  • safe.directory is a multi-valued config that marks repos as safe even if owned by another user; it’s only honored in protected configuration (system, global, and command scopes). [2][3]
  • Special forms:
    • Set to * to trust all repositories (disables the check). [3]
    • Append /* to trust all repos under a directory. [3]
    • ~/... and %(prefix)/... are interpolated/expanded. [3]
    • Add an empty safe.directory entry to reset/clear the list. [3]

A real-world use is setting safe.directory=* for a service via environment variables (e.g., systemd), using the GIT_CONFIG_COUNT/KEY/VALUE mechanism. [4]

Sources:
[1] git-config docs (ENVIRONMENT: GIT_CONFIG_COUNT, GIT_CONFIG_KEY_<n>, GIT_CONFIG_VALUE_<n>), Git SCM. (git-scm.com)
[2] git-config docs (SCOPES / Protected configuration; “command” scope includes env pairs and -c), Git SCM. (git-scm.com)
[3] git-config docs (safe.directory behavior, wildcards, interpolation, reset), Git SCM. (git-scm.com)
[4] Debian bug thread showing systemd Environment=GIT_CONFIG_COUNT=... safe.directory=* usage. (mail-archive.com)

Citations:


Scope safe.directory to workspace, not wildcard.

Using safe.directory=* trusts every repository in the container and weakens Git's ownership protection more than necessary. Set it to the mounted workspace path instead:

🎯 Narrower safe.directory configuration
-	args = append(args, "-e", "GIT_CONFIG_VALUE_0=*")
+	args = append(args, "-e", fmt.Sprintf("GIT_CONFIG_VALUE_0=%s", workingDir))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Configure git safe.directory to prevent "dubious ownership" errors
// when container user UID differs from mounted workspace file ownership
// (common with Colima, Podman, and other non-Docker-Desktop runtimes).
// Uses GIT_CONFIG_COUNT mechanism, additive to the mounted read-only .gitconfig.
args = append(args, "-e", "GIT_CONFIG_COUNT=1")
args = append(args, "-e", "GIT_CONFIG_KEY_0=safe.directory")
args = append(args, "-e", "GIT_CONFIG_VALUE_0=*")
// Configure git safe.directory to prevent "dubious ownership" errors
// when container user UID differs from mounted workspace file ownership
// (common with Colima, Podman, and other non-Docker-Desktop runtimes).
// Uses GIT_CONFIG_COUNT mechanism, additive to the mounted read-only .gitconfig.
args = append(args, "-e", "GIT_CONFIG_COUNT=1")
args = append(args, "-e", "GIT_CONFIG_KEY_0=safe.directory")
args = append(args, "-e", fmt.Sprintf("GIT_CONFIG_VALUE_0=%s", workingDir))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/runner.go` around lines 1003 - 1009, The current code appends
GIT_CONFIG_VALUE_0="*" which globally trusts all repos; change the value to the
mounted workspace path instead so only that workspace is marked safe. Update the
args append that sets GIT_CONFIG_VALUE_0 (the line using GIT_CONFIG_VALUE_0=*)
to use the workspace path variable used by the runner (e.g., the workspace or
repo path string the container mounts), ensuring it is properly escaped/quoted;
leave GIT_CONFIG_COUNT and GIT_CONFIG_KEY_0 as-is so the safe.directory entry is
added but scoped to the workspace.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Keeping the wildcard intentionally. This is a sandboxed dev container — scoping to the workspace path would break git submodules, nested repos, or any tools that clone repos elsewhere in the container. The security benefit of restricting safe.directory is negligible in an ephemeral sandbox.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@schuyler, that makes sense! You're right that in an ephemeral dev container where users may be working with submodules, nested repos, or tools that dynamically clone repositories, the wildcard approach is more practical. The sandbox isolation already provides the security boundary. Thanks for the clarification!

Comment thread README.md Resolved
os.MkdirTemp creates with 0700, preventing non-root container users
from accessing the directory on Linux (no transparent UID mapping).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
pkg/runner/e2e_test.go (1)

3870-3886: ⚠️ Potential issue | 🟠 Major

Isolate test credentials config instead of mutating real ~/.config/gh.

Line 3879 uses a fixed marker in the real user config, and Line 3884 can remove the whole directory. This can delete real data or race with other processes.

🛠️ Safer approach
-	ghConfigDir := filepath.Join(homeDir, ".config", "gh")
-	createdGHDir := false
-	if !fileExists(ghConfigDir) {
-		require.NoError(t, os.MkdirAll(ghConfigDir, 0755))
-		createdGHDir = true
-	}
-	markerFile := filepath.Join(ghConfigDir, "packnplay-test-marker")
-	require.NoError(t, os.WriteFile(markerFile, []byte("gh-creds-test\n"), 0644))
-	defer os.Remove(markerFile)
-	defer func() {
-		if createdGHDir {
-			os.RemoveAll(ghConfigDir)
-		}
-	}()
+	testHome, err := os.MkdirTemp(homeDir, "packnplay-e2e-home-*")
+	require.NoError(t, err)
+	defer os.RemoveAll(testHome)
+
+	oldHome := os.Getenv("HOME")
+	require.NoError(t, os.Setenv("HOME", testHome))
+	defer func() { _ = os.Setenv("HOME", oldHome) }()
+
+	ghConfigDir := filepath.Join(testHome, ".config", "gh")
+	require.NoError(t, os.MkdirAll(ghConfigDir, 0755))
+
+	markerFile := filepath.Join(ghConfigDir, fmt.Sprintf("packnplay-test-marker-%d", time.Now().UnixNano()))
+	require.NoError(t, os.WriteFile(markerFile, []byte("gh-creds-test\n"), 0644))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/e2e_test.go` around lines 3870 - 3886, The test must not mutate
the user's real ~/.config/gh; instead create an isolated temporary config dir
(use os.MkdirTemp) and point the test to it (set XDG_CONFIG_HOME or adjust
ghConfigDir usage) so fileExists, ghConfigDir and markerFile refer to the temp
location; write the marker there and defer os.RemoveAll on the temp dir (remove
createdGHDir logic and any removal of the real ~/.config/gh), ensuring cleanup
always runs and any env var set for the test is restored after the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@pkg/runner/e2e_test.go`:
- Around line 3870-3886: The test must not mutate the user's real ~/.config/gh;
instead create an isolated temporary config dir (use os.MkdirTemp) and point the
test to it (set XDG_CONFIG_HOME or adjust ghConfigDir usage) so fileExists,
ghConfigDir and markerFile refer to the temp location; write the marker there
and defer os.RemoveAll on the temp dir (remove createdGHDir logic and any
removal of the real ~/.config/gh), ensuring cleanup always runs and any env var
set for the test is restored after the test.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37f8a08 and b1fdfbd.

📒 Files selected for processing (1)
  • pkg/runner/e2e_test.go

- Use os.Remove instead of os.RemoveAll for gh config dir cleanup
- Update Requirements to list Colima and Podman as supported runtimes

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
pkg/runner/e2e_test.go (1)

3870-3886: ⚠️ Potential issue | 🟠 Major

Isolate GH config in a test sandbox; current marker flow can clobber user data.

At Line 3879, the test writes a fixed marker filename in real ~/.config/gh. If that file already exists, it is overwritten and then removed during cleanup.

🛠️ Safer test-local GH config approach
-	// Create ~/.config/gh with a marker file if it doesn't exist
-	homeDir, err := os.UserHomeDir()
+	// Create an isolated test HOME under the real home path so it remains mountable
+	realHomeDir, err := os.UserHomeDir()
 	require.NoError(t, err)
-	ghConfigDir := filepath.Join(homeDir, ".config", "gh")
-	createdGHDir := false
-	if !fileExists(ghConfigDir) {
-		require.NoError(t, os.MkdirAll(ghConfigDir, 0755))
-		createdGHDir = true
-	}
-	markerFile := filepath.Join(ghConfigDir, "packnplay-test-marker")
+	testHomeDir, err := os.MkdirTemp(realHomeDir, "packnplay-e2e-home-*")
+	require.NoError(t, err)
+	defer os.RemoveAll(testHomeDir)
+
+	oldHome := os.Getenv("HOME")
+	require.NoError(t, os.Setenv("HOME", testHomeDir))
+	defer func() {
+		if oldHome == "" {
+			_ = os.Unsetenv("HOME")
+		} else {
+			_ = os.Setenv("HOME", oldHome)
+		}
+	}()
+
+	ghConfigDir := filepath.Join(testHomeDir, ".config", "gh")
+	require.NoError(t, os.MkdirAll(ghConfigDir, 0755))
+	markerFile := filepath.Join(ghConfigDir, fmt.Sprintf("packnplay-test-marker-%d", time.Now().UnixNano()))
 	require.NoError(t, os.WriteFile(markerFile, []byte("gh-creds-test\n"), 0644))
 	defer os.Remove(markerFile)
-	defer func() {
-		if createdGHDir {
-			os.Remove(ghConfigDir)
-		}
-	}()

 	// Create temp dir under user's home so it's accessible inside VM-based Docker
 	// runtimes (Colima, etc.) which may only share /Users, not /var/folders
-	projectDir, err := os.MkdirTemp(homeDir, "packnplay-e2e-ghcreds-*")
+	projectDir, err := os.MkdirTemp(realHomeDir, "packnplay-e2e-ghcreds-*")
 	require.NoError(t, err)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/e2e_test.go` around lines 3870 - 3886, The test currently writes a
marker into the real ~/.config/gh (variables homeDir, ghConfigDir, markerFile,
and fileExists) which can clobber user data; change the test to create a
sandboxed config directory (use t.TempDir() or os.MkdirTemp) and set
XDG_CONFIG_HOME (or HOME) to that temp dir for the duration of the test, then
create the gh subpath and markerFile inside that sandbox instead of using
os.UserHomeDir(); ensure cleanup by letting the test temp dir remove itself and
restore any modified env var in a deferred function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@pkg/runner/e2e_test.go`:
- Around line 3870-3886: The test currently writes a marker into the real
~/.config/gh (variables homeDir, ghConfigDir, markerFile, and fileExists) which
can clobber user data; change the test to create a sandboxed config directory
(use t.TempDir() or os.MkdirTemp) and set XDG_CONFIG_HOME (or HOME) to that temp
dir for the duration of the test, then create the gh subpath and markerFile
inside that sandbox instead of using os.UserHomeDir(); ensure cleanup by letting
the test temp dir remove itself and restore any modified env var in a deferred
function.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1fdfbd and 3e4ecda.

📒 Files selected for processing (2)
  • README.md
  • pkg/runner/e2e_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md

…ile generator

Fixes staticcheck QF1012 lint violation.
@schuyler
schuyler force-pushed the fix/colima-compat branch from 58a0bbf to fc7ba31 Compare March 3, 2026 03:59
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.

1 participant