(MOT-4106) feat(github): GitHub CLI (gh) as an iii worker#529
(MOT-4106) feat(github): GitHub CLI (gh) as an iii worker#529andersonleal wants to merge 2 commits into
Conversation
30 typed github::pr/issue/repo/run/workflow/release/search functions plus github::exec (argv passthrough, outcome as data) and github::api (any REST endpoint). Bounded-capture exec core with process-group timeout kill (shell's run_to_completion mechanics), GH_TOKEN via the configuration worker with ambient gh-auth fallback, golden-tested wire schemas, and read-only functions allowed in iii-permissions (mutations and both escape hatches stay approval-gated).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
skill-check — worker0 verified, 47 skipped (no docs/).
Four for four. Nicely done. |
|
Warning Review limit reached
Next review available in: 25 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (18)
📝 WalkthroughWalkthroughAdds a Rust-based GitHub worker backed by the ChangesGitHub worker
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@github/src/functions/pr.rs`:
- Around line 150-163: Update CreateRequest so head is a required String rather
than an Option, then adjust the PR creation flow to always pass the request’s
head value to gh pr create via --head. Ensure all construction and handling of
CreateRequest provide and propagate head explicitly.
In `@github/src/functions/search.rs`:
- Around line 45-48: Update search_args to insert the argument separator "--"
before query, ensuring leading-negative queries are treated as search terms
rather than flags while preserving the existing kind, JSON, and limit arguments.
In `@github/src/gh.rs`:
- Around line 103-125: Update the timeout branch around kill_process_group and
child.start_kill to terminate the entire gh process tree on Windows, using a
Windows job object or equivalent descendant cleanup before awaiting stdout_task
and stderr_task. Preserve existing behavior on Unix and non-Windows platforms,
and add a test covering a spawned descendant that keeps stdout or stderr open
past the timeout.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d42fefe2-d135-4f07-9317-9c9803adb7da
⛔ Files ignored due to path filters (1)
github/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (58)
.github/workflows/create-tag.yml.github/workflows/release.ymlREADME.mdgithub/Cargo.tomlgithub/README.mdgithub/config.yamlgithub/iii-permissions.yamlgithub/iii.worker.yamlgithub/skills/SKILL.mdgithub/src/config.rsgithub/src/configuration.rsgithub/src/functions/actions.rsgithub/src/functions/issue.rsgithub/src/functions/mod.rsgithub/src/functions/passthrough.rsgithub/src/functions/pr.rsgithub/src/functions/release.rsgithub/src/functions/repo.rsgithub/src/functions/search.rsgithub/src/gh.rsgithub/src/lib.rsgithub/src/main.rsgithub/tests/contract.rsgithub/tests/golden/schemas/github.api.jsongithub/tests/golden/schemas/github.exec.jsongithub/tests/golden/schemas/github.issue.close.jsongithub/tests/golden/schemas/github.issue.comment.jsongithub/tests/golden/schemas/github.issue.create.jsongithub/tests/golden/schemas/github.issue.edit.jsongithub/tests/golden/schemas/github.issue.list.jsongithub/tests/golden/schemas/github.issue.view.jsongithub/tests/golden/schemas/github.pr.checks.jsongithub/tests/golden/schemas/github.pr.comment.jsongithub/tests/golden/schemas/github.pr.create.jsongithub/tests/golden/schemas/github.pr.diff.jsongithub/tests/golden/schemas/github.pr.edit.jsongithub/tests/golden/schemas/github.pr.list.jsongithub/tests/golden/schemas/github.pr.merge.jsongithub/tests/golden/schemas/github.pr.review.jsongithub/tests/golden/schemas/github.pr.view.jsongithub/tests/golden/schemas/github.release.create.jsongithub/tests/golden/schemas/github.release.list.jsongithub/tests/golden/schemas/github.release.view.jsongithub/tests/golden/schemas/github.repo.list.jsongithub/tests/golden/schemas/github.repo.view.jsongithub/tests/golden/schemas/github.run.cancel.jsongithub/tests/golden/schemas/github.run.list.jsongithub/tests/golden/schemas/github.run.rerun.jsongithub/tests/golden/schemas/github.run.view.jsongithub/tests/golden/schemas/github.search.code.jsongithub/tests/golden/schemas/github.search.issues.jsongithub/tests/golden/schemas/github.search.prs.jsongithub/tests/golden/schemas/github.search.repos.jsongithub/tests/golden/schemas/github.workflow.list.jsongithub/tests/golden/schemas/github.workflow.run.jsongithub/tests/schemas.rsgithub/tests/support/mod.rsiii-permissions.yaml
| pub struct CreateRequest { | ||
| /// Target repository, "owner/name". | ||
| pub repo: String, | ||
| /// Pull request title. | ||
| pub title: String, | ||
| /// Pull request body (markdown). | ||
| pub body: String, | ||
| /// Base branch to merge into (gh default: the repo default branch). | ||
| pub base: Option<String>, | ||
| /// Head branch the PR ships (pass explicitly; the worker has no checkout). | ||
| pub head: Option<String>, | ||
| /// Open as draft. | ||
| pub draft: Option<bool>, | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
For GitHub CLI 2.94, what happens when gh pr create -R owner/repoomits--head outside a Git working tree?
💡 Result:
When you run gh pr create owner/repo outside of a Git working tree, the GitHub CLI typically fails with an error indicating that it is not a Git repository [1][2][3]. This occurs because, historically, the gh pr create command was designed with a dependency on the local Git environment to determine context, even when an owner/repo argument or the --repo flag is provided [3]. Although newer versions of the GitHub CLI have made efforts to improve support for running commands outside of local repositories, the pr create command often still attempts to interact with Git internals behind the scenes, leading to errors like fatal: not a git repository [1][3]. If you omit the --head flag in this context, the command fails even more definitively because it cannot default to the "current branch" (a piece of information that only exists within a Git working tree) [3][4]. Even if you provide the --repo flag to target a specific remote repository, the command generally requires the existence of a local Git repository to proceed, as it expects to derive the head branch and other metadata from your local state [3]. To work around this limitation in environments where you are not inside a Git directory, you must generally ensure that the command has enough context to operate, or use alternative methods such as using the GitHub web interface or performing operations via the gh api command, which can sometimes bypass certain local Git requirements [2][3].
Citations:
- 1: Enable
gh pr createfrom outside of a local git repo cli/cli#6527 - 2: https://stackoverflow.com/questions/74216999/github-cli-create-pr-when-not-in-git-local-repo
- 3: Gh cli reports “not a git repository” when creating a pr using --repo param cli/cli#5075
- 4: https://cli.github.com/manual/gh_pr_create
Require head for standalone PR creation. gh pr create defaults --head to the current branch, so an optional field here accepts requests that cannot run reliably without a checkout. Make head required and always pass --head.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@github/src/functions/pr.rs` around lines 150 - 163, Update CreateRequest so
head is a required String rather than an Option, then adjust the PR creation
flow to always pass the request’s head value to gh pr create via --head. Ensure
all construction and handling of CreateRequest provide and propagate head
explicitly.
| fn search_args(kind: &str, json: &str, query: &str, limit: Option<u32>) -> Vec<String> { | ||
| let mut a = argv(["search", kind, query, "--json", json]); | ||
| push_opt(&mut a, "--limit", limit); | ||
| a |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
For GitHub CLI 2.94, must a search query beginning with -, such as -label:bug, follow the -- end-of-options marker?
💡 Result:
Yes, for GitHub CLI (gh) search commands, a search query that begins with a hyphen (such as -label:bug) must follow the -- end-of-options marker [1][2]. Because the GitHub CLI parser interprets arguments starting with a hyphen as command-line flags, it will fail to process them correctly if they are provided directly as part of the query string [1][3]. Using the -- separator explicitly tells the CLI that all subsequent arguments should be treated as non-option arguments (the query string) rather than flags [1][3]. For example, to search for issues without a specific label, you would use: gh search issues -- -label:bug [1][2] On PowerShell, additional care is required; you must use the --% stop-parse token followed by the -- marker to ensure the query is passed correctly: gh --% search issues -- -label:bug [1][4]
Citations:
- 1: https://cli.github.com/manual/gh_search
- 2: https://man.archlinux.org/man/gh-search-issues.1.en.raw
- 3: Enhance
gh searchcommands' help documentation to clarify use of--argument interpretation delimiter cli/cli#10480 - 4: Adding a note to
gh searchdocs to explain the usage of--to exclude certain results cli/cli#11162
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the relevant file first.
ast-grep outline github/src/functions/search.rs --view expanded || true
echo
echo "---- file ----"
cat -n github/src/functions/search.rs | sed -n '1,220p'
echo
echo "---- call sites ----"
rg -n "search_args\(" github/src -SRepository: iii-hq/workers
Length of output: 5440
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the current function and any neighboring logic if needed.
cat -n github/src/functions/search.rs | sed -n '1,180p'Repository: iii-hq/workers
Length of output: 4169
Protect leading-negative queries with --. gh search treats inputs like -label:bug as flags in this argv order; move the query after -- so negative qualifiers are passed through.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@github/src/functions/search.rs` around lines 45 - 48, Update search_args to
insert the argument separator "--" before query, ensuring leading-negative
queries are treated as search terms rather than flags while preserving the
existing kind, JSON, and limit arguments.
| let wait_res = tokio::time::timeout(timeout, child.wait()).await; | ||
|
|
||
| let (exit_code, timed_out) = match wait_res { | ||
| Ok(Ok(status)) => (status.code(), false), | ||
| Ok(Err(e)) => return Err(GhError::new("spawn_failed", format!("wait: {e}"))), | ||
| Err(_) => { | ||
| // Hard timeout: kill the whole process group (grandchildren too, | ||
| // e.g. the git/credential helpers gh spawns) while we still own | ||
| // the un-reaped child, then reap. start_kill is the non-unix | ||
| // fallback (kill_process_group is a no-op there). | ||
| kill_process_group(child.id()); | ||
| let _ = child.start_kill(); | ||
| let _ = child.wait().await; | ||
| (None, true) | ||
| } | ||
| }; | ||
|
|
||
| let (stdout_bytes, stdout_truncated) = stdout_task | ||
| .await | ||
| .map_err(|e| GhError::new("spawn_failed", format!("stdout: {e}")))?; | ||
| let (stderr_bytes, stderr_truncated) = stderr_task | ||
| .await | ||
| .map_err(|e| GhError::new("spawn_failed", format!("stderr: {e}")))?; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the relevant file and nearby symbols first
ast-grep outline github/src/gh.rs --view expanded || true
echo
echo "---- file length ----"
wc -l github/src/gh.rs
echo
echo "---- relevant sections ----"
sed -n '1,260p' github/src/gh.rs
echo
echo "---- search for process-group / timeout / kill / Windows references ----"
rg -n "kill_process_group|start_kill|timeout\\(|Job Object|windows|process group|stdout_task|stderr_task|wait_res" github/src -S
echo
echo "---- tests in github/src ----"
rg -n "#\\[test\\]|timeout|process group|windows|kill_process_group|start_kill" github/src -SRepository: iii-hq/workers
Length of output: 19651
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- search for job objects / Windows process cleanup elsewhere ----"
rg -n "Job Object|job object|CreateJobObject|AssignProcessToJobObject|kill_on_drop|process_group\\(|start_kill\\(|TerminateProcess|windows" . -S
echo
echo "---- inspect shell/src/exec/host.rs if present ----"
fd -a 'host.rs' shell . | head -n 20 || true
echo
echo "---- inspect github/src/gh.rs tests around timeout ----"
sed -n '260,320p' github/src/gh.rs
echo
echo "---- inspect config timeout resolution ----"
sed -n '1,120p' github/src/config.rsRepository: iii-hq/workers
Length of output: 50371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- gh.rs timeout test block ----"
sed -n '276,310p' github/src/gh.rs
echo
echo "---- any process-tree cleanup helper in shell/src/exec/host.rs ----"
if [ -f shell/src/exec/host.rs ]; then
sed -n '1,260p' shell/src/exec/host.rs | sed -n '1,220p'
else
echo "shell/src/exec/host.rs not present"
fi
echo
echo "---- any Windows-specific process cleanup code in github/src ----"
rg -n "process_group\\(|kill_process_group|Job Object|AssignProcessToJobObject|CreateJobObject|start_kill\\(" github/src -SRepository: iii-hq/workers
Length of output: 10790
🌐 Web query:
Tokio Child::start_kill Windows descendants inherited stdout stderr handles process tree semantics
💡 Result:
In Tokio, the Child::start_kill() method (which calls the underlying standard library std::process::Child::kill) does not provide process tree semantics for killing descendant processes on Windows [1][2][3]. It terminates only the specific child process represented by the Child handle [1][4]. Regarding process tree management and handle inheritance: 1. Process Tree Semantics: The tokio::process::Child::start_kill() method does not automatically kill descendant processes created by the child [4][2]. On Windows, terminating a process does not automatically terminate its descendants; those descendant processes will continue to run as orphaned processes unless explicitly managed by the parent or another supervisor. Tokio does not currently expose platform-specific Windows APIs (such as Job Objects) to enforce process tree termination [5][3]. 2. Stdin/Stdout/Stderr Handle Inheritance: By default, child processes created via tokio::process::Command inherit the handles of the parent process unless explicitly configured otherwise (e.g., using Stdio::piped()) [4][2]. When you use Stdio::piped(), Tokio creates anonymous pipes and manages the redirection of handles between the parent and child [1][3]. The parent process retains handles to these pipes, allowing it to asynchronously read from or write to the child's standard streams [1][4][3]. 3. Implementation Details: On Windows, Tokio uses RegisterWaitForSingleObject to asynchronously wait for the child process to exit [5][3]. The start_kill operation essentially invokes the standard library's TerminateProcess via the internal child handle [1][6][3]. It is explicitly documented that start_kill does not wait for the process to exit [4][7], and calling it after the process has already terminated is safe and will not result in an error [8][9]. If your application requires killing an entire process tree on Windows, you must implement this manually, typically by creating a Job Object and associating the child process with it, or by manually tracking and killing descendant processes [10].
Citations:
- 1: https://github.com/tokio-rs/tokio/blob/c637f6e7/tokio/src/process/windows.rs
- 2: https://github.com/tokio-rs/tokio/blob/306ed1c3/tokio/src/process/mod.rs
- 3: https://github.com/tokio-rs/tokio/blob/306ed1c3/tokio/src/process/windows.rs
- 4: https://docs.rs/tokio/latest/tokio/process/struct.Child.html
- 5: https://github.com/tokio-rs/tokio/blob/5030b300/tokio/src/process/windows.rs
- 6: std::process::Child::kill should not fail if process is terminated on Windows rust-lang/rust#112423
- 7: https://docs.rs/tokio/latest/%20tokio/process/struct.Child.html
- 8: Ok on Child::start_kill after wait tokio-rs/tokio#7160
- 9: https://github.com/tokio-rs/tokio/blob/master/tokio/CHANGELOG.md
- 10: process: add a
KillHandletokio-rs/tokio#2512
Kill the entire gh process tree on Windows.
start_kill() only stops the direct gh process there; a spawned helper can keep stdout/stderr open, so these drain awaits can run past the timeout. Use a Windows job object or equivalent process-tree cleanup, and add a descendant-timeout test.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@github/src/gh.rs` around lines 103 - 125, Update the timeout branch around
kill_process_group and child.start_kill to terminate the entire gh process tree
on Windows, using a Windows job object or equivalent descendant cleanup before
awaiting stdout_task and stderr_task. Preserve existing behavior on Unix and
non-Windows platforms, and add a test covering a spawned descendant that keeps
stdout or stderr open past the timeout.
…b page Five read-only panels over the github worker: pull requests (state filter + inline CI checks rollup via github::pr::checks), issues, Actions runs, releases, and org-wide search (repos/issues/prs/code). Repo input persists in localStorage; data loads on demand (no polling — gh rate limits). Nav entry gated on worker presence like worktrees/browser/memory; mutations deliberately stay in agent flows behind the approval gate. Console bumped to 1.6.11.
What
New standalone
githubworker (Rust,deploy: binary, v0.1.0) wrapping the GitHub CLI as iii functions:github::pr::*(list/view/create/edit/merge/comment/review/diff/checks),github::issue::*(list/view/create/edit/comment/close),github::repo::*(view/list),github::run::*(list/view/rerun/cancel),github::workflow::*(list/run),github::release::*(list/view/create),github::search::*(repos/issues/prs/code).github::exec(argv passthrough; non-zero exit and timeout are DATA —{stdout, stderr, exit_code, timed_out, *_truncated}) andgithub::api(any REST endpoint,-ffields / JSON body via--input -/--jq/--paginate).Design
shell/src/exec/host.rsmechanics: bounded capture that keeps draining after the cap (SIGPIPE guard preserves real exit codes), process-group SIGKILL on timeout,kill_on_drop, stdin piped only when supplied. Machine-branchable failure codes viaError::Remote(gh_not_found,spawn_failed,timeout).request -> argvbuilders (unit-tested without spawning),-R owner/namealways explicit (the worker has no checkout;repo view/listare positional), non-zero exits becomeError::Handlercarrying gh's stderr — exceptpr::checks, where exit codes {0,1,8} are check-state data.configurationworker (devin pattern):gh_executablePATH fallback,token: "${GH_TOKEN}"injected into child env with ambientgh auth loginfallback, timeout clamp (30s default / 120s max), 1 MiB per-stream caps. No gh probe at boot — interface collection works on bare runners.needs_approval;github::on-config-changedenied.Verification
cargo fmt --check,clippy -D warnings,cargo test(32 tests) all green.exec --version,repo::view cli/cli,pr::list,pr::checks(pending/failed as data),api rate_limit+--jqstring fallback, timeout-as-data, and the 404 error path all behave as designed.Wiring
create-tag.ymlworker option,release.ymltag glob, root README Modules row, rootiii-permissions.yamlrules. First release: Create Tag with worker=github, registry tagnext.Summary by CodeRabbit
New Features
Documentation
Chores