Runtime

How to let an AI agent open pull requests with the GitHub CLI from a sandbox

Install gh in the sandbox, store the token as a Runtime secret named GH_TOKEN, and the agent can push a branch and run gh pr create.

On Runtime gh works with a token the sandbox never holds. gh reads GH_TOKEN from the environment, and in a Runtime sandbox that variable holds a placeholder. The host's proxy swaps in the real token only on HTTPS requests to the GitHub hosts you name, so an agent that runs env, or a prompt injection that tries to post the token somewhere, finds nothing worth taking. GitHub CLI 2.101.0 was the current release on 25 September 2026.

Store the token and build an image with gh

Store the token once for the account. Then build gh into a custom image with the steps from GitHub's own Linux install guide, so no sandbox spends time installing it:

TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.secrets.set("GH_TOKEN", {  value: process.env.GITHUB_TOKEN ?? "",  hosts: ["api.github.com", "uploads.github.com"],});await runtime.images.build({  name: "gh",  recipe: {    commands: [      "mkdir -p -m 755 /etc/apt/keyrings" +        " && wget -nv -O /etc/apt/keyrings/githubcli-archive-keyring.gpg https://cli.github.com/packages/githubcli-archive-keyring.gpg" +        " && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg" +        ' && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list' +        " && apt-get update && apt-get install -y gh",    ],  },});
Pythonimport osfrom withruntime import Runtimeruntime = Runtime()runtime.secrets.set("GH_TOKEN", value=os.environ["GITHUB_TOKEN"],                    hosts=["api.github.com", "uploads.github.com"])keyring = "/etc/apt/keyrings/githubcli-archive-keyring.gpg"runtime.images.build(name="gh", recipe={"commands": [    f"mkdir -p -m 755 /etc/apt/keyrings && wget -nv -O {keyring} https://cli.github.com/packages/githubcli-archive-keyring.gpg"    f" && chmod go+r {keyring}"    f' && echo "deb [arch=$(dpkg --print-architecture) signed-by={keyring}] https://cli.github.com/packages stable main"'    " > /etc/apt/sources.list.d/github-cli.list && apt-get update && apt-get install -y gh",]})

Recipe commands run as root, so GitHub's sudo steps need no sudo here.

Install route Version on 25 September 2026 Command
Ubuntu 24.04's own gh package 2.45.0 sudo apt-get install -y gh
GitHub's apt repository 2.101.0 The recipe above

Ubuntu's package is quicker to type and 56 minor releases behind GitHub's.

The agent's pull request, end to end

gh handles the API; the branch itself goes up with git push, which needs the github.com header secret from Git in a sandbox:

TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await using sbx = await runtime.sandboxes.create({ image: "gh", timeoutSeconds: 1800 });const sh = (command: string) =>  sbx.exec(command, { cwd: "/workspace/app", check: true, timeoutMs: 300_000 });await sbx.exec("git clone https://github.com/acme/app.git app", {  check: true,  timeoutMs: 300_000,});await sh("git switch -c agent/update-readme");// ... the agent edits files here ...await sh(  "git -c user.name='Build Agent' -c user.email=agent@example.com commit -am 'Update the README'",);await sh("git push -u origin agent/update-readme");const pr = await sh(  "gh pr create --base main --head agent/update-readme --title 'Update the README' --body 'Opened by the build agent.'",);console.log(pr.stdout.trim()); // the pull request's URL
Pythonfrom withruntime import Runtimeruntime = Runtime()with runtime.sandboxes.create(image="gh", timeout_seconds=1800) as sbx:    def sh(command: str):        return sbx.exec(command, cwd="/workspace/app", check=True, timeout_ms=300_000)    sbx.exec("git clone https://github.com/acme/app.git app", check=True, timeout_ms=300_000)    sh("git switch -c agent/update-readme")    # ... the agent edits files here ...    sh("git -c user.name='Build Agent' -c user.email=agent@example.com commit -am 'Update the README'")    sh("git push -u origin agent/update-readme")    pr = sh("gh pr create --base main --head agent/update-readme"            " --title 'Update the README' --body 'Opened by the build agent.'")    print(pr.stdout.strip())

gh auth login is not needed: when GH_TOKEN is set, gh uses it for github.com and skips its login prompt. Do not run gh auth setup-git. It makes gh git's credential helper, which would hand git the placeholder as a password inside an encoded header that the proxy leaves alone; the header secret already covers git.

gh commands an agent uses most

Task Command
Read the issue it was given gh issue view 42 --json title,body,comments
Open the pull request gh pr create --base main --head <branch> --title ... --body ...
Wait for CI gh pr checks <branch> --watch
Read why CI failed gh run view <run-id> --log-failed
Answer a review gh pr comment <branch> --body ...
Anything else in the REST API gh api repos/{owner}/{repo}/...

--json output parses without scraping text, which suits a model. --watch blocks until checks finish, so give that command a timeoutMs long enough for your CI.

Keep the agent's reach small

  • The token: a fine-grained token can be limited to selected repositories, with "Contents" write for the push and "Pull requests" write for gh pr create, and nothing else.
  • The network: create the sandbox with network: { internet: true, allow: ["github.com", "api.github.com", "uploads.github.com"] } when the task needs nothing else. Root in the sandbox cannot change the rule.
  • The hosts: the secret's value goes only to the hosts it names. A request anywhere else carries the placeholder, which is worthless.

Sources

Checked 25 September 2026.

Facts on this page were checked on 25 September 2026.