Skip to content

Best Practices

Token Expiration

Installation tokens expire after 1 hour. For workflows longer than 50 minutes, regenerate tokens proactively to avoid mid-operation failures.

Best Practices

1. Proactive Refresh

# ✅ GOOD: Refresh at 55 minutes
- name: Check token age and refresh proactively
  run: |
    TOKEN_AGE_SECONDS=$(($(date +%s) - TOKEN_CREATED))
    if [ $TOKEN_AGE_SECONDS -gt 3300 ]; then
      # Refresh token
    fi

# ❌ BAD: Wait for 401 error
- run: |
    # API call fails at 60 minutes
    gh api user  # Error: Bad credentials

2. Minimize Token Generations

# ✅ GOOD: Generate once, share across jobs
jobs:
  setup:
    outputs:
      token: ${{ steps.token.outputs.token }}
  job-1:
    needs: setup
    env:
      GH_TOKEN: ${{ needs.setup.outputs.token }}
  job-2:
    needs: setup
    env:
      GH_TOKEN: ${{ needs.setup.outputs.token }}

# ❌ BAD: Generate token in every job
jobs:
  job-1:
    steps:
      - uses: actions/create-github-app-token@v2
  job-2:
    steps:
      - uses: actions/create-github-app-token@v2

3. Monitor Rate Limits

# ✅ GOOD: Check rate limits periodically
- run: |
    REMAINING=$(gh api /rate_limit --jq '.resources.core.remaining')
    if [ $REMAINING -lt 100 ]; then
      echo "::warning::Rate limit low: $REMAINING"
    fi

# ❌ BAD: No rate limit awareness
- run: |
    for i in {1..10000}; do
      gh api user  # Will hit rate limit
    done

4. Graceful Degradation

# ✅ GOOD: Implement backoff and retry
- run: |
    if ! gh api user; then
      echo "::notice::API call failed, waiting 60s"
      sleep 60
      gh api user  # Retry
    fi

# ❌ BAD: No error handling
- run: gh api user  # Fails workflow on error

Troubleshooting

Token Expired During Workflow

Error: Bad credentials (401)

Solution:

- name: Refresh expired token
  if: failure()
  id: refresh
  uses: actions/create-github-app-token@v2
  with:
    app-id: ${{ secrets.CORE_APP_ID }}
    private-key: ${{ secrets.CORE_APP_PRIVATE_KEY }}
    owner: adaptive-enforcement-lab

- name: Retry operation
  if: failure()
  env:
    GH_TOKEN: ${{ steps.refresh.outputs.token }}
  run: |
    # Retry failed operation
    gh api user

Rate Limit Exceeded

Error: API rate limit exceeded (403)

Solution:

- name: Wait for rate limit reset
  run: |
    RESET_TIME=$(gh api /rate_limit --jq '.resources.core.reset')
    CURRENT_TIME=$(date +%s)
    WAIT_TIME=$((RESET_TIME - CURRENT_TIME))

    if [ $WAIT_TIME -gt 0 ]; then
      echo "::notice::Waiting $((WAIT_TIME / 60)) minutes for rate limit reset"
      sleep $WAIT_TIME
    fi

Token Cache Stale

Problem: Cached token expired before dependent jobs started.

Solution:

# Add token age check before using cached token
- name: Validate cached token
  id: validate
  env:
    GH_TOKEN: ${{ needs.token-provider.outputs.token }}
  run: |
    if gh api user > /dev/null 2>&1; then
      echo "valid=true" >> $GITHUB_OUTPUT
    else
      echo "valid=false" >> $GITHUB_OUTPUT
    fi

- name: Generate fresh token if cache invalid
  if: steps.validate.outputs.valid != 'true'
  uses: actions/create-github-app-token@v2
  with:
    app-id: ${{ secrets.CORE_APP_ID }}
    private-key: ${{ secrets.CORE_APP_PRIVATE_KEY }}

References

Comments