Skip to content

[sync] go-process: 6 commits from Forge#1

Merged
Snider merged 6 commits intomainfrom
dev
Mar 17, 2026
Merged

[sync] go-process: 6 commits from Forge#1
Snider merged 6 commits intomainfrom
dev

Conversation

@Snider
Copy link
Copy Markdown
Contributor

@Snider Snider commented Mar 17, 2026

Forge → GitHub Sync

Commits: 6
Files changed: 12

Automated sync from Forge (forge.lthn.ai) to GitHub mirror.


Co-Authored-By: Virgil virgil@lethean.io

Summary by CodeRabbit

  • New Features

    • Added process detachment so processes can continue running independently of the parent.
  • Bug Fixes

    • Prevented a panic when setting a default to nil; now returns a controllable error.
  • Chores

    • Upgraded dependency versions and CI workflow.
    • Refactored internal error handling to emit structured logging errors for clearer diagnostics.

Snider and others added 6 commits March 15, 2026 15:36
Co-Authored-By: Virgil <virgil@lethean.io>
RunOptions.Detach creates the process in its own process group
(Setpgid) and uses context.Background() instead of the parent
context. Detached processes survive parent death.

Co-Authored-By: Virgil <virgil@lethean.io>
…nd registry

Replace all os.ReadFile, os.WriteFile, os.MkdirAll, os.Remove calls in
pidfile.go and registry.go with coreio.Local equivalents (Read, Write,
EnsureDir, Delete). Add forge.lthn.ai/core/go-io v0.1.2 as a direct dependency.

Co-Authored-By: Virgil <virgil@lethean.io>
Co-Authored-By: Virgil <virgil@lethean.io>
Replace all 27 instances of fmt.Errorf/errors.New in production code
with coreerr.E() from forge.lthn.ai/core/go-log for structured error
context (op, message, cause). Promote go-log from indirect to direct
dependency in go.mod.

Co-Authored-By: Virgil <virgil@lethean.io>
Co-Authored-By: Virgil <virgil@lethean.io>
@Snider
Copy link
Copy Markdown
Contributor Author

Snider commented Mar 17, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Refactors error handling to use structured coreerr.E across services, replaces direct filesystem calls with coreio Local methods, adds a Detach option to process RunOptions (detached process groups), updates tests to expect errors instead of panics, and bumps multiple module dependencies plus a new CI workflow.

Changes

Cohort / File(s) Summary
Daemon & lifecycle error handling
daemon.go, health.go, process.go, exec.go, runner.go
Replaced plain fmt.Errorf/errors.New usages with coreerr.E(...) wrappers; added coreerr import aliases. No public signatures changed.
Process service & options
service.go, types.go
Added Detach bool to RunOptions; StartWithOptions respects Detach (uses background context and sets process group via SysProcAttr.Setpgid); converted exported sentinel errors to coreerr.E.
File I/O abstraction
pidfile.go, registry.go
Replaced os/std file ops with coreio.Local methods (Read, Write, Delete, EnsureDir) and adjusted error wrapping to use coreerr.E.
Tests
global_test.go
Modified TestGlobal_SetDefault to expect a non-nil error when calling SetDefault(nil) instead of asserting a panic.
Dependencies / modules
go.mod
Bumped forge.lthn.ai/core modules and numerous indirect dependencies (gqlgen, go-openapi packages, opentelemetry, golang.org/x/*, etc.).
CI workflow
.github/workflows/ci.yml
Added GitHub Actions CI workflow with jobs for test, auto-fix and auto-merge (triggers on pushes and PR review events).
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 '[sync] go-process: 6 commits from Forge' accurately describes an automated sync pull request containing multiple commits from an upstream source, matching the PR's core objective and content.

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

📝 Coding Plan
  • Generate coding plan for human review comments

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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)
global_test.go (1)

64-65: ⚠️ Potential issue | 🟡 Minor

Unchecked error return from SetDefault.

The return value of SetDefault(svc) is ignored. Since the function now returns an error, consider using require.NoError(t, SetDefault(svc)) to ensure the happy path succeeds as expected.

Proposed fix
-		SetDefault(svc)
+		require.NoError(t, SetDefault(svc))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@global_test.go` around lines 64 - 65, SetDefault now returns an error but the
test ignores it; update the test to assert the call succeeds by replacing the
bare SetDefault(svc) with require.NoError(t, SetDefault(svc)) (and ensure the
testify/require import is present), then keep the existing assert.Equal(t, svc,
Default()) to verify the value.
🧹 Nitpick comments (1)
exec/exec.go (1)

157-163: Consider using a more descriptive location string.

The location string "wrapError" refers to the internal helper function rather than the calling context. This may reduce the usefulness of structured error tracing. Consider passing the location from the caller or using a more descriptive identifier like "exec.Command".

Proposed enhancement
-func wrapError(err error, name string, args []string) error {
+func wrapError(err error, name string, args []string, location string) error {
 	cmdStr := name + " " + strings.Join(args, " ")
 	if exitErr, ok := err.(*exec.ExitError); ok {
-		return coreerr.E("wrapError", fmt.Sprintf("command %q failed with exit code %d", cmdStr, exitErr.ExitCode()), err)
+		return coreerr.E(location, fmt.Sprintf("command %q failed with exit code %d", cmdStr, exitErr.ExitCode()), err)
 	}
-	return coreerr.E("wrapError", fmt.Sprintf("failed to execute %q", cmdStr), err)
+	return coreerr.E(location, fmt.Sprintf("failed to execute %q", cmdStr), err)
 }

Then update callers to pass their method name, e.g., wrapError(err, c.name, c.args, "Cmd.Run").

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

In `@exec/exec.go` around lines 157 - 163, The helper wrapError currently
hardcodes the location string "wrapError", reducing trace usefulness; change
wrapError signature to accept a location parameter (e.g., wrapError(err error,
name string, args []string, location string) error), use that location when
calling coreerr.E instead of the literal, and update all callers (e.g., Cmd.Run,
any call sites that pass err from exec.Command) to pass a descriptive location
like "exec.Command" or "Cmd.Run". Keep the existing ExitError handling (checking
exec.ExitError) and only replace the static "wrapError" identifier with the new
location argument when constructing the coreerr.E call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@daemon.go`:
- Around line 103-105: Start() currently sets d.running true and may acquire
PID/health resources before calling d.opts.Registry.Register(entry); if Register
fails you must rollback those startup resources and reset d.running. Add a
deferred/cleanup path in Start (e.g. a rollback closure captured before setting
d.running) that on any early return sets d.running = false and releases any
acquired resources: remove PID file, deregister health checks, and call
d.opts.Registry.Unregister(entry) if registration succeeded; invoke that
rollback when d.opts.Registry.Register(entry) returns an error so the daemon
leaves no leftover PID/health state on failed start.

In `@service.go`:
- Around line 116-119: The assignment to cmd.SysProcAttr with Setpgid is
Unix-only and fails on Windows; replace the direct construction with a
platform-specific helper: when opts.Detach is true set cmd.SysProcAttr =
detachedSysProcAttr(). Implement detachedSysProcAttr() in OS-specific files
(e.g., sysproc_unix.go with //go:build unix returning
&syscall.SysProcAttr{Setpgid: true} and sysproc_windows.go with //go:build
windows returning &syscall.SysProcAttr{}), keeping the symbol names
detachedSysProcAttr, cmd, opts.Detach and SysProcAttr as referenced in
service.go.

---

Outside diff comments:
In `@global_test.go`:
- Around line 64-65: SetDefault now returns an error but the test ignores it;
update the test to assert the call succeeds by replacing the bare
SetDefault(svc) with require.NoError(t, SetDefault(svc)) (and ensure the
testify/require import is present), then keep the existing assert.Equal(t, svc,
Default()) to verify the value.

---

Nitpick comments:
In `@exec/exec.go`:
- Around line 157-163: The helper wrapError currently hardcodes the location
string "wrapError", reducing trace usefulness; change wrapError signature to
accept a location parameter (e.g., wrapError(err error, name string, args
[]string, location string) error), use that location when calling coreerr.E
instead of the literal, and update all callers (e.g., Cmd.Run, any call sites
that pass err from exec.Command) to pass a descriptive location like
"exec.Command" or "Cmd.Run". Keep the existing ExitError handling (checking
exec.ExitError) and only replace the static "wrapError" identifier with the new
location argument when constructing the coreerr.E call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f5aa3c59-da7e-4cf3-8510-02c3214b6036

📥 Commits

Reviewing files that changed from the base of the PR and between 3694f6e and d955ffc.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (11)
  • daemon.go
  • exec/exec.go
  • global_test.go
  • go.mod
  • health.go
  • pidfile.go
  • process.go
  • registry.go
  • runner.go
  • service.go
  • types.go

Comment thread daemon.go
Comment on lines 103 to 105
if err := d.opts.Registry.Register(entry); err != nil {
return fmt.Errorf("registry: %w", err)
return coreerr.E("Daemon.Start", "registry", err)
}
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

Rollback daemon state when registry registration fails.

At Line 104, Start() returns after d.running is already true and startup resources may already be acquired. This can leave PID/health resources active after a failed start.

Suggested fix
 		if err := d.opts.Registry.Register(entry); err != nil {
+			if d.health != nil {
+				_ = d.health.Stop(context.Background())
+			}
+			if d.pid != nil {
+				_ = d.pid.Release()
+			}
+			d.running = false
 			return coreerr.E("Daemon.Start", "registry", err)
 		}
📝 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
if err := d.opts.Registry.Register(entry); err != nil {
return fmt.Errorf("registry: %w", err)
return coreerr.E("Daemon.Start", "registry", err)
}
if err := d.opts.Registry.Register(entry); err != nil {
if d.health != nil {
_ = d.health.Stop(context.Background())
}
if d.pid != nil {
_ = d.pid.Release()
}
d.running = false
return coreerr.E("Daemon.Start", "registry", err)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@daemon.go` around lines 103 - 105, Start() currently sets d.running true and
may acquire PID/health resources before calling d.opts.Registry.Register(entry);
if Register fails you must rollback those startup resources and reset d.running.
Add a deferred/cleanup path in Start (e.g. a rollback closure captured before
setting d.running) that on any early return sets d.running = false and releases
any acquired resources: remove PID file, deregister health checks, and call
d.opts.Registry.Unregister(entry) if registration succeeded; invoke that
rollback when d.opts.Registry.Register(entry) returns an error so the daemon
leaves no leftover PID/health state on failed start.

Comment thread service.go
Comment on lines +116 to +119
// Detached processes get their own process group
if opts.Detach {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}
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:

#!/bin/bash
set -euo pipefail

echo "Build tags present in Go files:"
rg -n --type go '^//go:build' -C1

echo
echo "SysProcAttr/Setpgid usage:"
rg -n --type go 'SysProcAttr|Setpgid' -C2

Repository: dAppCore/go-process

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Find the service.go file
fd -name "service.go" -type f

Repository: dAppCore/go-process

Length of output: 293


🏁 Script executed:

#!/bin/bash
# Find the service.go file
fd service.go

Repository: dAppCore/go-process

Length of output: 73


🏁 Script executed:

#!/bin/bash
# Check the file size first
wc -l service.go

# Read the file with line numbers to see context around lines 116-119
cat -n service.go

Repository: dAppCore/go-process

Length of output: 11329


🏁 Script executed:

#!/bin/bash
# Check for existing OS-specific file patterns
fd -type f -name '*_unix.go' -o -name '*_windows.go' -o -name '*_darwin.go' -o -name '*_linux.go'

echo "---"

# Check if there are any build tags in the codebase at all
find . -name "*.go" -type f -exec grep -l "//go:build" {} \;

Repository: dAppCore/go-process

Length of output: 254


🏁 Script executed:

#!/bin/bash
# Check for existing OS-specific file patterns
find . -name '*_unix.go' -o -name '*_windows.go' -o -name '*_darwin.go' -o -name '*_linux.go'

echo "---"

# Check if there are any build tags in the codebase at all
find . -name "*.go" -exec grep -l "//go:build" {} \;

Repository: dAppCore/go-process

Length of output: 66


🏁 Script executed:

#!/bin/bash
# Check all Go files in the repo
ls -la *.go 2>/dev/null || echo "No .go files in root"

# List all Go files to understand structure
find . -name "*.go" -type f | head -20

Repository: dAppCore/go-process

Length of output: 1638


🌐 Web query:

syscall.SysProcAttr Setpgid Windows Go Unix compatibility

💡 Result:

  • syscall.SysProcAttr.Setpgid is a Unix-only knob (it maps to the POSIX setpgid behavior). In Go’s Unix SysProcAttr, you can see Setpgid bool and Pgid int documented/implemented. [2]
  • On Windows, syscall.SysProcAttr is a different struct and does not have Setpgid/Pgid (so &syscall.SysProcAttr{Setpgid:true} won’t compile on Windows). Windows’ SysProcAttr instead exposes fields like CreationFlags, HideWindow, etc. [1]

What to do for cross-platform code (Go):

  1. Split per-OS code (recommended): put the Setpgid setup in *_unix.go (or *_linux.go, *_darwin.go, etc.) and a Windows alternative in *_windows.go.
  2. If your goal on Windows is “don’t share the parent’s console control handling / be able to signal the child group”, the closest primitive is starting the process with SysProcAttr.CreationFlags = syscall.CREATE_NEW_PROCESS_GROUP. [1][3]
    Note: this is not equivalent to Unix process groups/signals; Windows doesn’t implement POSIX PGIDs/signals the same way.

🏁 Script executed:

#!/bin/bash
# Verify by checking Go stdlib documentation for SysProcAttr
# Check what fields are available and if this would cause compilation issues
python3 << 'EOF'
# Quick verification: Setpgid is platform-specific
info = """
In Go's syscall package:
- unix/linux/darwin: syscall.SysProcAttr has Setpgid field (bool)
- windows: syscall.SysProcAttr does NOT have Setpgid field

This means the code at line 118 will cause a compilation error on Windows:
'field Setpgid not defined on SysProcAttr type'

The code needs OS-specific handling as suggested in the review.
"""
print(info)
EOF

Repository: dAppCore/go-process

Length of output: 403


Guard Setpgid assignment behind OS-specific handling.

Line 118 uses a Unix-only SysProcAttr field directly. This causes a compilation error on Windows, where syscall.SysProcAttr lacks the Setpgid field.

Suggested fix
-	if opts.Detach {
-		cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
-	}
+	if opts.Detach {
+		cmd.SysProcAttr = detachedSysProcAttr()
+	}

Use OS-specific helpers:

// sysproc_unix.go
//go:build unix

package process

import "syscall"

func detachedSysProcAttr() *syscall.SysProcAttr {
	return &syscall.SysProcAttr{Setpgid: true}
}
// sysproc_windows.go
//go:build windows

package process

import "syscall"

func detachedSysProcAttr() *syscall.SysProcAttr {
	return &syscall.SysProcAttr{}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service.go` around lines 116 - 119, The assignment to cmd.SysProcAttr with
Setpgid is Unix-only and fails on Windows; replace the direct construction with
a platform-specific helper: when opts.Detach is true set cmd.SysProcAttr =
detachedSysProcAttr(). Implement detachedSysProcAttr() in OS-specific files
(e.g., sysproc_unix.go with //go:build unix returning
&syscall.SysProcAttr{Setpgid: true} and sysproc_windows.go with //go:build
windows returning &syscall.SysProcAttr{}), keeping the symbol names
detachedSysProcAttr, cmd, opts.Detach and SysProcAttr as referenced in
service.go.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
.github/workflows/ci.yml (1)

41-44: Prefer queued auto-merge behind protection checks.

The current workflow merges immediately upon CodeRabbit approval (line 52) without waiting for any branch protection rules. Use the --auto flag to enable auto-merge, which respects required status checks, required reviews, and merge queues before allowing the merge.

Suggested safer merge mode
-        run: gh pr merge ${{ github.event.pull_request.number }} --merge --delete-branch
+        run: gh pr merge ${{ github.event.pull_request.number }} --auto --merge --delete-branch

Also applies to: 52-54

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

In @.github/workflows/ci.yml around lines 41 - 44, The workflow currently
performs an immediate merge on CodeRabbit approval; update the merge step (the
step running the GitHub CLI `gh pr merge` command) to use the queued auto-merge
mode by adding the `--auto` flag (and keep the desired merge method flag such as
`--merge`/`--squash`/`--rebase` if present) so it respects branch protection,
required checks and merge queues instead of merging immediately; locate the step
that executes `gh pr merge` and replace its invocation with the `--auto`
variant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/ci.yml:
- Line 17: Replace mutable action refs that use the `@dev` tag with immutable
commit SHA pins: locate occurrences of "dAppCore/build/actions/build/core@dev"
(and the second occurrence noted in the review) and change them to the
corresponding full commit SHAs (e.g., @<full-commit-sha>) obtained from the
dAppCore repository for the exact commit you want to lock to; update both
occurrences so CI uses the same immutable SHAs and commit the changes so
workflows are reproducible and hardened against supply-chain shifts.
- Around line 23-35: The job is not fork-safe: the checkout uses
github.event.pull_request.head.ref (in the actions/checkout@v4 step) which can
resolve incorrectly for forked PRs and the workflow runs even when GITHUB_TOKEN
is read-only for forks; update the job guard to only run when the PR head repo
equals the base repo (e.g., require
github.event.pull_request.head.repo.full_name == github.repository) so the job
skips forked PRs, and change the checkout to use
github.event.pull_request.head.sha instead of head.ref for deterministic
checkout of the exact commit.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 41-44: The workflow currently performs an immediate merge on
CodeRabbit approval; update the merge step (the step running the GitHub CLI `gh
pr merge` command) to use the queued auto-merge mode by adding the `--auto` flag
(and keep the desired merge method flag such as `--merge`/`--squash`/`--rebase`
if present) so it respects branch protection, required checks and merge queues
instead of merging immediately; locate the step that executes `gh pr merge` and
replace its invocation with the `--auto` variant.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b2e71d3c-7459-4da0-a75a-3dce1cb37dcd

📥 Commits

Reviewing files that changed from the base of the PR and between d955ffc and 365197f.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml

Comment thread .github/workflows/ci.yml Outdated
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dAppCore/build/actions/build/core@dev
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:

#!/bin/bash
# Identify workflow actions pinned to mutable refs (branch/tag) instead of full commit SHAs.
fd -t f -e yml -e yaml .github/workflows | \
  xargs rg -nP '^\s*-\s*uses:\s*[^@]+@(?!(?:[0-9a-fA-F]{40})\b)'

Repository: dAppCore/go-process

Length of output: 438


🏁 Script executed:

# First, find and list YAML files in .github/workflows directory
fd . '.github/workflows' -t f -e yml -e yaml

Repository: dAppCore/go-process

Length of output: 87


🏁 Script executed:

# Check specific lines in ci.yml if file exists
if [ -f ".github/workflows/ci.yml" ]; then
  echo "=== Lines 1-40 of ci.yml ===";
  head -n 40 ".github/workflows/ci.yml" | cat -n;
fi

Repository: dAppCore/go-process

Length of output: 1286


🏁 Script executed:

# Check for other workflow files and count total lines in ci.yml
wc -l ".github/workflows/ci.yml"

# Search all workflow files for mutable refs (fixing the original command syntax)
fd . '.github/workflows' -t f \( -name '*.yml' -o -name '*.yaml' \) | \
  xargs rg -nP '^\s*-\s*uses:\s*[^@]+@(?!(?:[0-9a-fA-F]{40})\b)' 2>/dev/null || echo "Search completed"

Repository: dAppCore/go-process

Length of output: 355


🏁 Script executed:

# Search for all 'uses:' lines in workflow files
fd . '.github/workflows' -t f | \
  xargs rg 'uses:' -A 0 -B 0

# Also check specifically for `@dev`, `@main`, `@v` tags (mutable refs)
rg '@(dev|main|master|latest|[a-z]+-[a-z]+)\b' '.github/workflows/ci.yml' -n

Repository: dAppCore/go-process

Length of output: 364


Pin workflow actions to immutable SHAs.

Lines 17 and 36 use @dev, which is mutable. This makes CI behaviour non-reproducible and increases supply-chain risk.

Suggested hardening
-      - uses: dAppCore/build/actions/build/core@dev
+      - uses: dAppCore/build/actions/build/core@<full_commit_sha>
-      - uses: dAppCore/build/actions/fix@dev
+      - uses: dAppCore/build/actions/fix@<full_commit_sha>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml at line 17, Replace mutable action refs that use
the `@dev` tag with immutable commit SHA pins: locate occurrences of
"dAppCore/build/actions/build/core@dev" (and the second occurrence noted in the
review) and change them to the corresponding full commit SHAs (e.g.,
@<full-commit-sha>) obtained from the dAppCore repository for the exact commit
you want to lock to; update both occurrences so CI uses the same immutable SHAs
and commit the changes so workflows are reproducible and hardened against
supply-chain shifts.

Comment thread .github/workflows/ci.yml Outdated
Comment on lines +23 to +35
if: >
github.event_name == 'pull_request_review' &&
github.event.review.user.login == 'coderabbitai' &&
github.event.review.state == 'changes_requested'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
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

🌐 Web query:

For GitHub Actions triggered by pull_request_review, what is the correct checkout configuration for forked pull requests, and can GITHUB_TOKEN push commits to fork branches?

💡 Result:

Correct actions/checkout for forked PRs on pull_request_review

For pull_request_review, the event payload includes the PR, so you should explicitly checkout the PR head repo + head SHA (not the default refs/pull/.../merge in the base repo):

- uses: actions/checkout@v6
  with:
    repository: ${{ github.event.pull_request.head.repo.full_name }}
    ref:        ${{ github.event.pull_request.head.sha }}
    fetch-depth: 0

This pattern (using head.repo.full_name + head.ref/head.sha) is the documented way to checkout code from a fork PR. [1] [2]

Can GITHUB_TOKEN push commits to fork branches?

No, not in the general case. On workflows triggered by a PR from a fork, GitHub adjusts the GITHUB_TOKEN so that any write permissions become read-only (unless a repo admin explicitly enables Send write tokens to workflows from pull requests). [3]

And even when you do have write permissions, GITHUB_TOKEN is for the workflow’s repository context; trying to git push back into a contributor’s fork typically fails with Permission ... denied to github-actions[bot]. [4]

If you need to push commits to the contributor’s fork branch, you generally must use different credentials that have write access to that fork (e.g., a PAT or GitHub App installation token with access to the fork). [3] [4]

Sources:
[1] actions/checkout README (checkout PR head SHA) (github.com)
[2] actions/checkout issue showing fork checkout config (repository + ref) (github.com)
[3] GitHub Docs: forked PR runs make GITHUB_TOKEN write perms read-only (unless “Send write tokens…” enabled) (docs.github.com)
[4] GitHub Community: pushing to a fork with GITHUB_TOKEN fails with permission denied (github.com)

Citations:


auto-fix is not fork-safe as written.

Line 34 checks out only head.ref without specifying the fork repository. For forked PRs, this often resolves against the base repo and fails. Additionally, GITHUB_TOKEN has write permissions reduced to read-only on forked PR workflows by default, preventing commits being pushed back to contributor branches.

Suggested guard + deterministic checkout
   auto-fix:
     if: >
       github.event_name == 'pull_request_review' &&
       github.event.review.user.login == 'coderabbitai' &&
-      github.event.review.state == 'changes_requested'
+      github.event.review.state == 'changes_requested' &&
+      github.event.pull_request.head.repo.full_name == github.repository
...
       - uses: actions/checkout@v4
         with:
-          ref: ${{ github.event.pull_request.head.ref }}
+          ref: ${{ github.event.pull_request.head.sha }}
           fetch-depth: 0

The guard prevents this job from running on forked PRs, ensuring the workflow only executes where push access is available. Using head.sha instead of head.ref provides deterministic checkout behaviour.

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

In @.github/workflows/ci.yml around lines 23 - 35, The job is not fork-safe: the
checkout uses github.event.pull_request.head.ref (in the actions/checkout@v4
step) which can resolve incorrectly for forked PRs and the workflow runs even
when GITHUB_TOKEN is read-only for forks; update the job guard to only run when
the PR head repo equals the base repo (e.g., require
github.event.pull_request.head.repo.full_name == github.repository) so the job
skips forked PRs, and change the checkout to use
github.event.pull_request.head.sha instead of head.ref for deterministic
checkout of the exact commit.

@Snider
Copy link
Copy Markdown
Contributor Author

Snider commented Mar 17, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Snider Snider merged commit c2606f2 into main Mar 17, 2026
1 check passed
Snider pushed a commit that referenced this pull request Mar 22, 2026
…LAUDE.md — update any outdate...' (#1) from agent/dx-audit-and-fix--1--review-claude-md into main
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