GitHub Repository Checkout

The checkout: frontmatter field controls how actions/checkout is invoked in the agent job. Configure custom checkout settings, check out multiple repositories, or disable checkout entirely.

By default, the agent checks out the repository where the workflow is running with a shallow fetch (fetch-depth: 1). If triggered by a pull_request event, it also checks out the PR’s selected immutable commit, as described in PR Revision Binding. For pull_request_target events, automatic PR checkout is disabled by default; pull-request-floating-head does not enable it. For most workflows, this default checkout is sufficient and no checkout: configuration is necessary.

Before the agent job, activation separately checks out workflow configuration to load runtime imports and skills. On pull_request, pull_request_review, and pull_request_review_comment events, this sparse checkout uses the PR base SHA, not the PR head; on other events it retains the normal activation ref. For reusable workflow_call workflows, it uses the resolved callee SHA when there is no active PR event, and skips cross-repository checkout if authentication may fall back to the caller’s repository-scoped GITHUB_TOKEN. The checkout: options below describe the agent job and do not override this activation checkout.

Use checkout: when you need to check out additional branches, check out multiple repositories, or to disable checkout entirely for workflows that don’t need to access code or can access code dynamically through the GitHub Tools.

By default, the compiler-generated “Checkout PR branch” step fetches a specific commit SHA from the current repository’s origin, then verifies the checked-out HEAD before reporting success or recording a patch baseline. It does not resolve a moving refs/pull/N/head during fetch.

TriggerSelected revision by defaultHead movement
pull_request, except labeled and unlabeledEvent-recorded PR head SHARetains the event revision, even if the branch moves or is deleted.
pull_request label eventsEvent-recorded PR head SHAMust match the live head before and after fetch.
pull_request_review / pull_request_review_commentReview or review-comment commit_idMust match the live head before and after fetch.
Revision-bound workflow_dispatchSupplied aw_context.pr_head_shaMust match the live head before and after fetch.
issue_comment, routed comment commands, or manual PR dispatch without revision evidenceCurrent API head SHAPins the selected head and rejects a detected change during fetch; does not establish revision approval.

These rules apply only when the compiler generates automatic PR checkout. The floating-head opt-in replaces event/review revision selection with current-head selection.

Centralized command/label dispatch propagates event-recorded revision evidence in aw_context.pr_head_sha. A revision-bound dispatch must provide a full 40-character commit SHA matching the current PR head. Missing required evidence, malformed PR context, API failures, and detected head changes stop checkout rather than continuing on the previous workspace.

Issue comments contain no historical PR head SHA. Comment commands therefore authorize current-head execution, not approval of a particular revision. They require valid comment creation/edit and PR update timestamps and warn when the PR was updated after the triggering comment; the resolved head is pinned and checked again after fetch. Timestamps cannot prove which revision was reviewed: unrelated activity, including the router’s status comment, also updates the PR. Manual PR dispatch without command/label markers or a supplied revision also selects the current head; it is not revision approval. Workflows requiring revision approval should use a revision-bearing PR/review event or disable automatic PR checkout with checkout: { pull-request: false }. A comment command alone is not suitable for approving privileged execution of PR code.

These checks preserve the existing actor permission floor and same-repository bot restrictions. A matching SHA proves commit identity, not that PR code is safe to execute with credentials. Fetching still requires credentials available to the checkout step; the helper does not persist credentials, widen sparse paths, or unshallow a repository.

After successful checkout, the unified session records a workflow.pr_checkout_revision event from runtime/pr-checkout with prNumber and the full, lowercase selectedSha. This records commit identity without adding repository content or credentials; checkout log messages use 12-character SHA prefixes.

To select the current PR head at execution time instead of the triggering event or review revision, explicitly opt in:

checkout:
pull-request-floating-head: true

The default is false. The value must be a literal boolean; strings such as "true" and expressions are rejected. With true, checkout ignores event/review SHA and comment freshness evidence, resolves the current head through the PR API, and uses the current commit count for fetch depth. It still fetches that specific SHA, rechecks the head after fetch, and verifies actual HEAD. A head change during fetch fails; this option does not restore mutable-ref fetching or bypass the server’s SHA-fetch requirements.

Actor permissions, fork/bot restrictions, dispatch repository/shape validation, and safe-output lifecycle checks remain enforced. The setting applies workflow-wide when any entry in a checkout: list enables it; it does not enable checkout when checkout: false, pull-request: false, or the compiler’s PR-target policy suppresses the PR checkout step. Recompile the workflow with the updated compiler/runtime to apply the setting.

You can use checkout: to override default checkout settings (e.g., fetch depth, sparse checkout) without needing to define a custom job:

checkout:
fetch-depth: 0 # Full git history
github-token: ${{ secrets.MY_TOKEN }} # Custom authentication

Or use GitHub App authentication:

checkout:
fetch-depth: 0
github-app:
client-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}

You can also use checkout: to check out additional repositories alongside the main repository:

checkout:
- fetch-depth: 0
- repository: owner/other-repo
path: ./libs/other
ref: main
github-token: ${{ secrets.CROSS_REPO_PAT }}
FieldTypeDescription
pull-requestbooleanSet false to disable automatic PR checkout and its paired base-config snapshot/restore steps. The normal repository checkout remains enabled.
pull-request-floating-headbooleanSelect the current API head instead of event/review revision evidence. Defaults to false; literal boolean only. Applies workflow-wide if any checkout entry enables it. See Allow Floating Head.
repositorystringExplicit repository in owner/repo format, or a newline-separated list with multi: true. Without multi, expressions must resolve to exactly one repository and need no allowlist. Does not support wildcards. Defaults to the current repository.
repostringAlias for repository; specify only one. Does not support wildcards.
multibooleanExplicitly enables runtime repository-list expansion and its restrictions. Defaults to false; must be a literal boolean.
allowed-reposstring | string[]Mandatory allowlist for multi: true; does not enable multi mode by itself. Accepts newline-separated patterns, an array, or an expression; supports scoped patterns such as owner/* and */repo, but not unrestricted "*" or "*/*". Ignored in single-repository mode.
max-reposintegerMaximum unique repositories per multi: true checkout entry. Requires allowed-repos in multi mode; defaults to 10. Must be a positive literal integer. Ignored in single-repository mode.
allow-private-reposbooleanWith multi: true, allow private/internal targets in a runtime list hosted by a private/internal repository. Requires allowed-repos in multi mode; defaults to false and must be a literal boolean. Public hosts always reject non-public multi targets, even when true. Ignored in single-repository mode.
refstringBranch, tag, or SHA to checkout. Defaults to the triggering ref.
pathstringPath within GITHUB_WORKSPACE to place the checkout. Defaults to workspace root.
github-tokenstringToken for authentication. Use ${{ secrets.MY_TOKEN }} syntax.
github-appobjectGitHub App credentials (client-id or app-id (deprecated), private-key, optional owner, repositories). Mutually exclusive with github-token. app is a deprecated alias for the field name. Run gh aw fix to auto-migrate app-id to client-id.
safe-outputs-github-appobjectOptional per-checkout GitHub App credentials used exclusively for safe_outputs git operations on this checkout target (client-id or app-id (deprecated), private-key, optional owner, repositories). Does not change agent/activation checkout auth. See Cross-Organization safe_outputs Authentication for cross-org usage.
fetch-depthintegerCommits to fetch. 0 = full history, 1 = shallow clone (default).
fetchstring | string[]Additional Git refs to fetch after checkout. See Fetching Additional Refs.
sparse-checkoutstringNewline-separated patterns for sparse checkout (e.g., .github/\nsrc/).
submodulesstring/boolSubmodule handling: "recursive", "true", or "false".
lfsbooleanDownload Git LFS objects.
currentbooleanMarks this checkout as the primary working repository. The agent uses this as the default target for all GitHub operations. Only one checkout may set current: true; the compiler rejects workflows where multiple checkouts enable it.
force-clean-git-credentialsbooleanWhen true, the checkout step is generated with persist-credentials: true and followed by a dedicated cleanup step that scrubs both repo and submodule git credentials. Use this for submodule-heavy or sparse checkouts where the default persist-credentials: false post-step cleanup fails. See Cleaning Submodule Credentials.

An entry with multi: true expands repository (or repo) at runtime into one checkout per newline-separated owner/repo slug. It requires allowed-repos. This allows scheduled workers to process a changing repository set supplied through variables, inputs, or earlier step outputs.

Without multi: true (including multi: false), the runtime requires exactly one owner/repo slug. Repository expressions do not require allowed-repos or a non-root path prefix. Normal single-checkout authentication, private-repository access, LFS, submodules, current, and wiki behavior applies; none of the multi-repo restrictions below apply. Merely specifying allowed-repos does not enable expansion or constrain a single checkout.

checkout:
repo: ${{ inputs.repo }}
path: target
current: true

repository and repo select explicit repository names, not wildcard patterns. Their expressions must resolve to concrete owner/repo slugs. Wildcards are supported only in allowed-repos, which filters the selected list and never discovers or selects repositories.

allowed-repos policies may use literal patterns or standalone references to repository or organization variables, such as ${{ vars.ALLOWED_WORKER_REPOS }}. Caller-controlled inputs, step or job outputs, computed expressions, and expressions combined with literal text are rejected at compilation. Only the selected repository list may come from caller-controlled data.

on:
schedule: daily
checkout:
- multi: true
repository: ${{ vars.WORKER_REPOS }}
path: targets
allowed-repos: ${{ vars.ALLOWED_WORKER_REPOS }}
max-repos: 5
github-token: ${{ secrets.CROSS_REPO_PAT }}

For WORKER_REPOS containing org/api and org/web on separate lines, the checkouts are placed at targets/org/api and targets/org/web. With multi: true, path must be a literal, non-root prefix and allowed-repos is mandatory, even if the selector is literal or resolves to just the workflow repository. The allowlist may contain scoped wildcard patterns such as org/* or */api. Unrestricted patterns such as * and */* are rejected, including when an expression resolves to them at runtime. Matching and deduplication are case-insensitive; an empty selected list produces no additional checkouts, not all allowlisted repositories.

Every selected repository must pass the allowlist and GitHub API metadata checks before the checkout action is generated. Redirected repository names are checked against the allowlist again. Runtime lists allow only public targets by default. Set allow-private-repos: true on an entry to also allow private/internal targets when the hosting repository is private/internal. Public workflow repositories cannot check out private or internal repositories, even when this flag is true and the supplied token can access them. The flag requires allowed-repos and a literal boolean; it does not change ordinary static checkouts. Both checkout jobs enforce the same policy, and merged entries permit non-public targets only if every entry explicitly opts in. Omission or false prevents an imported entry from broadening access. Failed or incomplete visibility checks stop the job.

Enabled submodules are rejected at compilation and runtime for all repository lists because repository-controlled submodule URLs can bypass allowed-repos using the checkout token. Generated checkouts and follow-up fetches disable submodule recursion without removing repository-controlled .gitmodules files. Omit submodules or set it to false; static checkout submodule support is unchanged.

max-repos caps each runtime entry at 10 unique selected slugs by default, after case-insensitive deduplication and before metadata requests or checkout. Oversized lists fail the entire batch; they are not truncated. Set a positive literal integer to change the cap. Entries merged for the same repository expression and path use the smallest effective cap, including the default when omitted. Separate runtime entries have independent caps.

A disallowed repository fails the entire checkout step before any selected repository is cloned; it is never silently skipped. The rejection is included in the workflow’s agent failure issue, subject to the existing safe-outputs.report-failure-as-issue settings. Invalid runtime selectors or allowlists, unsafe paths, missing credentials, failed visibility checks, and exceeded repository limits also report their preparation failure through this path. Unexpected dependency errors report a generic preparation failure rather than exposing raw dependency diagnostics in the issue. Failures in the agent and safe-output checkout jobs both use this reporting path.

Runtime entries support the normal ref, history, sparse-checkout, fetch, and authentication options. lfs: true is rejected at compilation and runtime for all repository lists: repository-controlled LFS endpoints could download content outside allowed-repos using the checkout token. Static checkouts continue to support LFS. GitHub App authentication requires explicit owner and repositories scoping. current: true and wiki: true are not supported for repository lists. Existing destination directories and symlinked path ancestors are rejected rather than cleaned or followed.

The agent and PR safe-output jobs each build a checkout manifest recording the expanded repositories, their exact workspace paths, and their default branches. Safe-output handlers use that manifest to locate repositories even under deep prefixes. Before agent execution, finalization explicitly cleans and verifies every expanded checkout’s git config and submodule configs using the validated paths, regardless of prefix depth. Missing configs, unavailable cleanup helpers, and residual credentials stop the job. PR safe-output jobs use the same expansion and directory layout, retaining their effective PR checkout credentials, including global safe-output GitHub App authentication unless an entry specifies its own credentials; the agent receives credential-free checkouts.

By default, actions/checkout performs a shallow clone (fetch-depth: 1) of a single ref. For workflows that need to work with other branches — for example, a scheduled workflow that must push changes to open pull-request branches — use the fetch: option to retrieve additional refs after the checkout step.

A dedicated git fetch step is emitted after the actions/checkout step. Authentication re-uses the checkout token (or falls back to github.token) via a transient http.extraheader credential — no credentials are persisted to disk, consistent with the enforced persist-credentials: false policy.

Each fetch: pattern is checked and fetched independently, for both static and runtime-selected repositories. Patterns that match no complete remote ref names are skipped without preventing other patterns from being fetched; suffix-only matches do not count. Authentication, network, and other discovery or fetch errors still fail the step. Additional fetches use the checkout’s effective fetch-depth (1 by default; 0 for full history). Expression-valued patterns are passed as data, not interpolated into shell scripts.

For example, fetch: ["main", "my-prefix/*"] fetches main even when no branches match my-prefix/*. A missing literal branch is also skipped; an unmatched pattern does not create a local ref.

ValueDescription
"*"All remote branches.
"refs/pulls/open/*"All open pull-request head refs (GH-AW shorthand).
"main"A specific branch name.
"feature/*"A glob pattern matching branch names.
checkout:
- fetch: ["*"] # fetch all branches (default checkout)
fetch-depth: 0 # fetch full history to ensure we can see all commits and PR details
checkout:
- repository: githubnext/gh-aw-side-repo
github-token: ${{ secrets.GH_AW_SIDE_REPO_PAT }}
fetch: ["refs/pulls/open/*"] # fetch all open PR refs after checkout
fetch-depth: 0 # fetch full history to ensure we can see all commits and PR details
checkout:
- repository: org/target-repo
github-token: ${{ secrets.CROSS_REPO_PAT }}
fetch: ["main", "feature/*"] # fetch specific branches
fetch-depth: 0 # fetch full history to ensure we can see all commits and PR details

The generated checkout step uses persist-credentials: false, so the git credentials that actions/checkout used are removed once checkout completes. The agent then runs without credentials for the checked-out repository, and any git operation that must authenticate to the remote fails. In private repositories this includes:

  • git fetch, git pull, git clone, and direct git push
  • Checking out or switching to a remote branch that was not already fetched
  • Deepening a shallow clone (git fetch --unshallow)
  • On-demand blob fetches in partial (blobless) clones — operations on files absent from the initial checkout

Fetch everything the workflow needs at checkout time using fetch-depth and fetch:, and write changes through safe-output tools such as push-to-pull-request-branch rather than a direct git push. The agent is instructed not to configure credential helpers or run git credential fill, because authentication cannot succeed; credential errors are reported as a limitation instead of worked around.

Set checkout: false to suppress both the default actions/checkout step and the PR-specific “Checkout PR branch” step entirely. Use this for workflows that access repositories through MCP servers or other mechanisms that do not require a local clone:

checkout: false

This is equivalent to omitting the checkout step from the agent job. Custom dev-mode steps (such as “Checkout actions folder”) are unaffected.

PR head checkout fetches the immutable commit selected by the revision-binding rules, including for same-repository PRs. Source-branch deletion does not change the selected revision, but checkout still requires that commit to be available from origin. A queued review rejects a detected head change instead of checking out a later revision. The local branch keeps the PR’s source branch name; checkout does not recreate the remote branch or fall back to the base branch.

A failed PR checkout stops the agent job; it never continues on the previous base checkout. If an originally open PR closes and fetching the selected commit fails with a missing-ref or upload-pack: not our ref <sha> error, a live closure check records lifecycle invalidation separately from ordinary checkout errors. Authentication, permission, network, unknown errors, and unavailable state evidence remain failures. Workflows triggered on an already closed or merged PR still require a successful checkout of the selected commit.

For work triggered while a PR was open, the safe-output manager checks live PR state before each handler, deferred retry, and buffered review submission. If the PR has closed, remaining effects are recorded as skipped, not applied. Closed-event workflows and staged previews are preserved. Configured safe-output tokens need read access to the triggering PR; a failed state lookup is an error, not proof of closure. PR-branch pushes also refuse to recreate deleted target branches. These checks cannot make a PR-state lookup and a later GitHub write atomic.

For body-only review loops, a bot explicitly listed in on.bots can trigger PR checkout through pull_request: edited without pushing a commit. The event sender must be a Bot whose login matches the triggering actor, and the runtime, PR head, and PR base repository IDs must match. Unlisted bots and fork PRs still require write-or-higher collaborator permission. Filter body edits with an if: condition on github.event.changes.body to avoid re-reviewing unrelated edits.

To keep the normal repository checkout but suppress the compiler-generated “Checkout PR branch” step and its associated base-config snapshot/restore steps, set pull-request: false under checkout:

checkout:
pull-request: false

Target-Only Checkout (permissions.contents: none)

Section titled “Target-Only Checkout (permissions.contents: none)”

Sidecar (MultiRepoOps) workflows often need to operate on a target repository without checking out the repository that hosts the workflow itself. Setting permissions.contents: none suppresses only the default workflow-repository checkout and the “Checkout PR branch” step; any other explicitly configured checkout: entries (such as a target repository) are still checked out normally:

permissions:
contents: none
checkout:
- repository: octo-org/target-repository
path: target
github-app:
client-id: ${{ vars.TARGET_APP_CLIENT_ID }}
private-key: ${{ secrets.TARGET_APP_PRIVATE_KEY }}
owner: octo-org
repositories:
- target-repository

This differs from checkout: false, which disables checkout entirely (including any additional configured repositories). With permissions.contents: none, only the workflow’s own repository is skipped.

Safe-output patch and bundle generation is local-only. It never fetches missing refs or objects from the agent-writable checkout, even when safe-output API credentials are configured. All Git transports, including external remote helpers and partial-clone lazy fetches, are disabled during artifact generation. Repository-configured external diffs, text conversion, hooks, fsmonitor, and signing are also disabled. Filtered artifacts are synthesized from committed objects with index-only plumbing, without checkout or clean/smudge filters. Raw repo-memory staging pins parent-directory descriptors before reading files; it requires Linux descriptor-relative paths and fails closed on other platforms. Prepare the required refs with checkout.fetch and sufficient history with checkout.fetch-depth before agent execution; partial clones must also have the required blobs locally. Incremental updates require the pre-agent PR-head baseline recorded by the PR checkout step or an existing origin/<branch> tracking ref. Missing baselines are reported rather than replaced with a broader diff. Full-mode generation can use the local base branch when its origin tracking ref is absent, including cross-repository checkouts. Filtered bundles preserve the checkout’s shallow boundaries; deeper history is only needed when the required baseline cannot be resolved locally. Failure messages retain fixed checkout and workflow-compilation guidance without exposing raw Git stderr, checkout paths, or caller-supplied ref names.

When local generation cannot resolve required refs or objects, non-staged PR outputs can instead return result: "queued" with recovery: "object-pack". This is not publication success. The agent packages available local objects without fetching; the trusted safe-outputs job fetches the authorized baseline into a new isolated bare repository, validates the objects, and regenerates the patch and bundle before applying the existing file, size, and publication policies. Agent repository configuration, remotes, hooks, and alternates are never copied.

The fallback selects objects reachable from the pinned tip, using available local baseline refs as exclusion hints. It omits unrelated branches, tags, unreachable history, and objects already reachable from those hints. Hints reduce transfer size but never authorize a publisher baseline or a remote request. The default 100,000-object and 64 MiB uncompressed limits apply to the selected objects, not the entire monorepo. The existing patch artifact previews every packed object. Recovery rejects redacted or mismatched previews. After redaction and before upload, the artifact guard also blocks recovery envelopes whose paired previews changed or disappeared. Publisher rejection alone cannot protect bytes already available in a downloadable artifact.

By default, the trusted publisher starts with a depth-one, treeless fetch of the authorized baseline. If required commit ancestry is absent, it increases depth geometrically through 16, 64, 256, 1,024, 4,096, 16,384, and at most 65,536 commits, keeping the first resolved revision pinned. Only the required trusted baseline/boundary tree snapshots are then fetched without blobs, rather than downloading every intermediate monorepo tree. It compares trees before importing agent objects and fetches only required old/new blobs whose identities were independently read from trusted fetched trees. Unchanged large files are not hydrated; missing agent-created bytes are rejected rather than requested from upload hints. Servers without filtering support fail explicitly instead of retrying an unfiltered clone. Authentication and other fetch failures are not retried.

All fetching finishes before agent objects are imported. By default, requests have a 60-second command deadline and share a 180-second network-operation deadline. Tree inspection is limited to one million entries, required baseline blob hydration to 64 MiB, and the uploaded pack/import to 80 MiB. Hydration size is checked after transfer; these limits are not a downloaded-byte budget.

GitHub Actions repository configuration variables (Settings > Secrets and variables > Actions > Variables) configure the following budgets without changing workflow frontmatter. The compiler binds them explicitly into the agent runtime, nested safe-outputs MCP container, upload-validation steps, and trusted publisher. Upload validation and publishing independently use repository values; uploaded metadata and agent-written environment files cannot override their limits. Existing compiled workflows require recompilation with the matching setup runtime.

Repository variableDefaultControls
GH_AW_OBJECT_PACK_MAX_BYTES67108864Uncompressed objects, hydration, and derived pack/preview/header/output-buffer bounds
GH_AW_OBJECT_PACK_MAX_OBJECTS100000Selected/parsed objects and derived tree/scan entry and fetch-batch budgets
GH_AW_OBJECT_PACK_TIMEOUT_MS60000Each command and the derived shared network deadline
GH_AW_OBJECT_PACK_FETCH_DEPTHS1,16,64,256,1024,4096,16384,65536Finite, strictly increasing history search depths

For byte budget B, pack/import and Git graph buffers are ceil(5B/4), previews are 2B, total envelope headers are max(1, floor(B/16384)), diagnostic buffers are max(1, floor(B/64)), and nested regeneration Git buffers are ceil(25B/16). Uncompressed object and post-transfer hydration budgets are each B. For object budget N, tree and scan entry budgets are each 10N, and fetch batches contain at most max(1, ceil(256N/100000)) IDs. The shared network deadline is three times the command timeout. These ratios preserve all existing defaults: 80 MiB packs, 128 MiB previews, 4 KiB headers, 1 MiB diagnostic buffers, 100 MiB nested Git buffers, one million tree/scan entries, 256-ID batches, and 180 seconds for network operations.

Empty or unset variables retain defaults. Numeric settings accept positive decimal integers only; zero does not disable a limit. Depths accept a comma-separated strictly increasing list of positive integers. Invalid settings fail closed instead of being ignored or clamped. Node.js buffer/string and subprocess-timer capacities, Git’s depth range, and the pack format’s object-count range still apply. Configured budgets must also keep every derived buffer and timer representable.

For example, gh variable set GH_AW_OBJECT_PACK_MAX_BYTES --body 134217728 raises the byte budget to 128 MiB, packs to 160 MiB, and previews to 256 MiB. Larger budgets can increase runner memory, CPU, and network use; no setting relaxes repository authorization, preview validation, native object validation, or the fetch-before-import boundary.

Incremental recovery requires the triggering PR’s exact pre-agent head SHA and an authorized head repository; an uploaded baseline or a newer remote head cannot replace it. Dynamic PR updates without that trusted event baseline, staged outputs, larger selected changes, exhausted recovery budgets, and missing agent-created content still require checkout provisioning. Full recovery uses the authorized base branch and preserves the trusted dispatch-SHA baseline for same-repository feature runs. Publisher credentials must authorize the fetched repository independently of any credentials previously used by actions/checkout.

Fallback issues keep the original object-pack uploads immutable. Their recovery commands require a clean, complete clone and reconstruct a binary patch from the publisher-resolved baseline and tip, retaining configured exclusions. Object-pack envelopes and previews cannot be applied directly with git fetch or git am.

GitHub Actions debug logging includes [object_pack_transport] resolution stages, decision booleans, and object/byte counts. These traces omit repository identities, refs, commit IDs, filesystem paths, credentials, object contents, and raw subprocess diagnostics.

For pull_request_target workflows, the “Checkout PR branch” step is auto-disabled when no checkout: key is present in frontmatter. This prevents hard failures when the PR head branch is deleted (merged/closed events) or inaccessible (fork PRs).

To check out a trusted ref such as the base SHA, opt in explicitly with a checkout: mapping:

on:
pull_request_target:
types: [opened, synchronize]
checkout:
repository: ${{ github.repository }}
ref: ${{ github.event.pull_request.base.sha }}

This produces a configured checkout pointing at the safe base ref; the “Checkout PR branch” step remains disabled for pull_request_target. Do not use github.event.pull_request.head.sha or refs/pull/.../head in a privileged pull_request_target job — that would check out untrusted code in a context with write permissions.

Strict mode rejects pull_request_target checkouts unless checkout is explicitly disabled, targets the supported base repository/ref, or matches a literal pair in on.pull_request_target.allowed-checkouts. Declare this policy in the main workflow’s frontmatter; it is not inherited from imports. The default restrictions remain unchanged when the policy is omitted.

on:
pull_request_target:
types: [closed]
branches: [main]
forks: ["dotnet/*"]
acknowledge-risk: true
allowed-checkouts:
- repository: dotnet/AspNetCore.Docs
ref: main
if: github.event.pull_request.merged == true
checkout:
- repository: dotnet/AspNetCore.Docs
ref: main
path: .
current: true

on.pull_request_target.allowed-checkouts defaults to no additional trusted checkouts. Each entry requires a literal owner/repository and a non-empty literal branch, tag, or commit SHA. Matching is exact and case-sensitive; repository/ref pairs cannot be combined across entries. Expressions, wildcards, and refs/pull/... refs are rejected in the policy. Every configured checkout must independently satisfy the policy or the existing base-checkout rules. Do not configure checkout.fetch for these checkouts: follow-up fetches can add refs beyond the trusted checkout, so they do not satisfy the policy. External repositories with omitted or PR-dependent refs remain rejected. Wiki checkouts are not covered by this allowlist.

on.pull_request_target.acknowledge-risk defaults to false. Setting it to true suppresses only the strict-mode dangerous-trigger warning, including for checkout: false or a supported base checkout. It neither allows other checkouts nor bypasses fork-pattern validation, actor authorization, the unrestricted-forks warning, other strict validation, or safe-update checks. An allowlist without acknowledgment still emits the trigger warning.

Marking a Primary Repository (current: true)

Section titled “Marking a Primary Repository (current: true)”

When a workflow running from a central repository targets a different repository, use current: true to tell the agent which repository to treat as its primary working target. The agent uses this as the default for all GitHub operations (creating issues, opening PRs, reading content) unless the prompt instructs otherwise. When omitted, the agent defaults to the repository where the workflow is running.

checkout:
- repository: org/target-repo
path: ./target
github-token: ${{ secrets.CROSS_REPO_PAT }}
current: true # agent's primary target

By default, generated checkout steps set persist-credentials: false, which causes actions/checkout to remove credentials in its post-step. In repositories with submodules or sparse checkouts, that post-step can fail with missing submodule URL or path errors.

Set force-clean-git-credentials: true on a checkout target to opt into an explicit cleanup step instead. The compiler emits the checkout with persist-credentials: true, then injects a Clean git credentials after checkout step immediately after it. The cleanup removes the credential helper and http.*.extraheader entries from both .git/config and any .git/modules/*/config, including nested submodules.

Cleanup is fail-closed: a failed helper, invalid git configuration, or residual credential helper, authentication header, or authenticated URL stops the job before agent execution. A separate read-only verification checks the effective configuration, including included config files, without logging token values. The final pre-agent credential cleanup has the same verification requirement. With no checkout or git config files, cleanup and verification are no-ops.

checkout:
- repository: org/monorepo-with-submodules
submodules: recursive
force-clean-git-credentials: true

Cross-Organization safe_outputs Authentication (safe-outputs-github-app)

Section titled “Cross-Organization safe_outputs Authentication (safe-outputs-github-app)”

By default, the safe_outputs job uses GITHUB_TOKEN to check out repositories. The only exception is the default workspace checkout when the global safe-outputs.github-app serves create-pull-request or push-to-pull-request-branch for the workflow repository (no target-repo/head-repo, no per-handler app or token): it persists the minted App token so pushes run as the App and trigger workflows. Additional checkouts, per-handler apps and cross-repository targets never inherit the global App token for checkout, which prevents cross-org token confusion when the app is scoped to a different organization.

For workflows that target a different organization, use safe-outputs-github-app on the relevant checkout entry to supply a checkout token for the safe_outputs job:

checkout:
- repository: OrgB/target-repo
github-app:
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: OrgB
safe-outputs-github-app:
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: OrgB
current: true
safe-outputs:
github-app:
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: OrgB
create-pull-request:
target-repo: OrgB/target-repo

In this configuration:

  • The agent job checks out OrgB/target-repo using the app token scoped to OrgB.
  • The safe_outputs job checks out OrgB/target-repo using the safe-outputs-github-app token.
  • The safe_outputs job checks out the workflow repository using GITHUB_TOKEN (the default), not the OrgB-scoped app token — avoiding the cross-org authentication failure.
  • safe-outputs.github-app is used only for PR creation and push operations, not for checkout.

Multiple checkout: configurations can target the same path and repository. This is useful for monorepos where different parts of the repository must be merged into the same workspace directory with different settings (e.g., sparse checkout for some paths, full checkout for others).

When multiple checkout: entries target the same repository and path, their configurations are merged with the following rules:

  • Fetch depth: Deepest value wins (0 = full history always takes precedence)
  • Fetch refs: Merged (union of all patterns; duplicates are removed)
  • Sparse patterns: Merged (union of all patterns)
  • LFS: OR-ed (if any config enables lfs, the merged configuration enables it)
  • Submodules: First non-empty value wins for each (repository, path); once set, later values are ignored
  • Ref/Token/App: First-seen wins