This error occurs in GitHub Actions when the actions/checkout step fails to clone or access a repository. Exit code 128 indicates a fatal Git error, typically caused by authentication issues, missing repository permissions, or invalid references.
Exit code 128 in Git is a catch-all for "fatal" errors - situations where Git cannot proceed with the requested operation. When this appears in GitHub Actions, it means the `actions/checkout` action (or a manual `git` command) encountered an unrecoverable error. The error occurs during the repository checkout phase of your workflow, before your actual build or test steps run. GitHub Actions uses a special token (GITHUB_TOKEN) to authenticate with repositories, and this token has specific permissions and limitations. Common scenarios that trigger exit code 128: 1. **Authentication failure** - The workflow doesn't have permission to access the repository 2. **Repository not found** - The repo doesn't exist, is private without proper access, or the reference is wrong 3. **Invalid reference** - Trying to checkout a branch, tag, or commit that doesn't exist 4. **Submodule issues** - Submodules require additional authentication configuration 5. **Shallow clone conflicts** - Operations that require full history on a shallow clone 6. **LFS problems** - Large File Storage files can't be fetched due to bandwidth or authentication
The default GITHUB_TOKEN has limited permissions. For private repos or cross-repo access, you need to configure explicit permissions:
Add permissions to your workflow:
name: CI
on: [push, pull_request]
permissions:
contents: read # Required for checkout
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4For accessing other private repositories:
permissions:
contents: read
packages: read # If accessing GitHub Packages
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.PAT_TOKEN }} # Personal Access Token with repo scopeCreate a Personal Access Token (PAT):
1. Go to GitHub Settings > Developer settings > Personal access tokens > Fine-grained tokens
2. Select the repositories you need access to
3. Grant "Contents: Read" permission
4. Add the token as a repository secret (Settings > Secrets > Actions)
Exit code 128 often occurs when trying to checkout a non-existent reference:
Check your checkout configuration:
# Incorrect - branch might not exist
- uses: actions/checkout@v4
with:
ref: feature/nonexistent-branch # This will fail if branch doesn't exist
# Correct - use default or verify branch exists
- uses: actions/checkout@v4 # Checks out the triggering ref
# Or explicitly check the correct branch
- uses: actions/checkout@v4
with:
ref: mainFor pull requests, use the correct ref:
# For PR workflows - checkout the PR merge commit
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
# Or the PR branch
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}Debug by printing the ref:
- name: Debug ref
run: |
echo "github.ref: ${{ github.ref }}"
echo "github.sha: ${{ github.sha }}"
echo "github.head_ref: ${{ github.head_ref }}"Submodules are a common cause of exit code 128 because they require separate authentication:
Enable submodule checkout:
- uses: actions/checkout@v4
with:
submodules: true # or 'recursive' for nested submodules
token: ${{ secrets.PAT_TOKEN }} # PAT with access to submodule reposFor submodules using SSH URLs:
- uses: actions/checkout@v4
with:
submodules: true
ssh-key: ${{ secrets.SUBMODULE_SSH_KEY }}Generate SSH key for submodules:
1. Generate a key: ssh-keygen -t ed25519 -C "github-actions"
2. Add the public key as a Deploy Key to each submodule repository
3. Add the private key as a secret in the main repository
Convert SSH to HTTPS URLs in workflow:
- uses: actions/checkout@v4
- name: Convert submodule URLs
run: |
git config --global url."https://github.com/".insteadOf "[email protected]:"
git submodule update --init --recursive
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}Pull requests from forks have limited access to secrets and permissions by default:
For public repositories:
on:
pull_request_target: # Runs in the context of the base repo
types: [opened, synchronize]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}Warning about pull_request_target:
Using pull_request_target runs workflow code from the base branch with access to secrets. Never run untrusted code from the fork in this context.
Safer approach - split workflows:
# For safe operations (checkout, build)
on: pull_request
# For operations needing secrets (deploy, comment)
on:
workflow_run:
workflows: ["Build"]
types: [completed]Check if running from fork:
- name: Check if fork
run: |
if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
echo "This is a fork PR"
fiOlder versions of actions/checkout have known bugs and missing features:
Use the latest version (v4):
# Old - may have issues
- uses: actions/checkout@v2
# Current - recommended
- uses: actions/checkout@v4Version differences:
- v1: Deprecated, uses deprecated Git protocols
- v2: Works but lacks some features
- v3: Node 16, good compatibility
- v4: Node 20, latest features and fixes
Common issues fixed in newer versions:
- Better handling of sparse checkout
- Improved submodule authentication
- Fixed issues with detached HEAD
- Better performance with partial clones
Pin to specific version for stability:
# Pin to exact version
- uses: actions/[email protected]
# Or use major version (recommended)
- uses: actions/checkout@v4Large File Storage (LFS) files can cause checkout failures:
Enable LFS in checkout:
- uses: actions/checkout@v4
with:
lfs: trueIf LFS quota exceeded:
# Skip LFS files if not needed
- uses: actions/checkout@v4
with:
lfs: false
# Or fetch only specific LFS files
- uses: actions/checkout@v4
- name: Fetch specific LFS files
run: |
git lfs pull --include="path/to/needed/files/*"Check LFS status:
- uses: actions/checkout@v4
with:
lfs: true
- name: Verify LFS
run: |
git lfs ls-files
git lfs statusLFS bandwidth issues:
- GitHub Free: 1 GB storage, 1 GB bandwidth/month
- Consider caching LFS files between runs
- Or host large files elsewhere
By default, actions/checkout performs a shallow clone (depth=1). Some operations need more history:
Fetch full history:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for all branches and tagsFetch specific depth:
# Fetch last 10 commits
- uses: actions/checkout@v4
with:
fetch-depth: 10When you need full history:
- Running git describe
- Generating changelogs
- Operations that traverse commit history
- Some version detection tools
Fetch specific refs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true # Ensure tags are fetchedPartial clone for large repos:
- uses: actions/checkout@v4
with:
filter: blob:none # Don't fetch file contents initiallyAdd debugging to understand exactly what's failing:
Enable Git trace logging:
- uses: actions/checkout@v4
env:
GIT_TRACE: 1
GIT_CURL_VERBOSE: 1
GIT_TRACE_PACKET: 1
# Or for more detail
- name: Debug checkout
run: |
GIT_TRACE=2 GIT_CURL_VERBOSE=1 git clone https://github.com/${{ github.repository }}.git
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Check token permissions:
- name: Check token scopes
run: |
curl -sS -f -I -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com | grep -i x-oauth-scopesList available refs:
- name: List refs
run: |
git ls-remote https://github.com/${{ github.repository }}.git
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Manual clone to isolate issue:
- name: Manual clone
run: |
git clone --depth 1 https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git repo
cd repo && git status### Understanding GITHUB_TOKEN Permissions
The default GITHUB_TOKEN in GitHub Actions has specific permission boundaries:
Same repository: Full read/write access (configurable)
Other repositories: No access by default
Forks (pull_request event): Read-only, no secrets access
# Explicit permissions (recommended)
permissions:
actions: read
contents: read
issues: write
pull-requests: write### Organization-Wide Private Repositories
For accessing multiple private repos in an organization:
1. Create a GitHub App with necessary permissions
2. Install the App in your organization
3. Generate installation tokens in workflows:
- uses: tibdex/github-app-token@v1
id: app-token
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
repository: org/private-repo### SAML SSO Requirements
If your organization uses SAML SSO:
1. Personal Access Tokens must be authorized for SSO
2. Go to token settings and click "Authorize" next to your organization
3. GitHub Apps bypass SSO requirements
### Checkout Performance Optimization
For large repositories:
- uses: actions/checkout@v4
with:
fetch-depth: 1
filter: blob:none
sparse-checkout: |
src/
package.json
sparse-checkout-cone-mode: true### Self-Hosted Runners
On self-hosted runners, Git configuration persists:
- uses: actions/checkout@v4
with:
clean: true # Ensure clean working directory
persist-credentials: false # Don't store credentials### Common Exit Codes Reference
| Exit Code | Meaning |
|-----------|---------|
| 1 | Generic error |
| 128 | Fatal error (auth, not found, invalid ref) |
| 129 | Invalid options |
### Git Protocol Selection
Force HTTPS instead of SSH for submodules:
- name: Configure Git to use HTTPS
run: |
git config --global url."https://github.com/".insteadOf [email protected]:
git config --global url."https://github.com/".insteadOf ssh://[email protected]/fatal: bad object in rev-list input
Git rev-list encounters bad or invalid object
fatal: Out of memory, malloc failed during pack operation
Out of memory during Git pack operation
kex_exchange_identification: Connection closed by remote host
Connection closed by remote host when connecting to Git server
fatal: unable to access: Proxy auto-configuration failed
How to fix 'Proxy auto-configuration failed' in Git
fatal: unable to access: Authentication failed (proxy requires basic auth)
How to fix 'Authentication failed (proxy requires basic auth)' in Git