Stage 1: Repository Discovery¶
Query repositories via GraphQL to determine distribution targets.
Implementation¶
jobs:
discover:
name: Discover target repositories
runs-on: ubuntu-latest
outputs:
repositories: ${{ steps.query.outputs.repos }}
count: ${{ steps.query.outputs.count }}
steps:
- name: Generate authentication token
id: auth
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.CORE_APP_ID }}
private-key: ${{ secrets.CORE_APP_PRIVATE_KEY }}
owner: your-org
- name: Query team repositories
id: query
env:
GH_TOKEN: ${{ steps.auth.outputs.token }}
run: |
REPOS=$(gh api graphql -f query='
{
organization(login: "your-org") {
team(slug: "target-team") {
repositories(first: 100) {
nodes {
name
defaultBranchRef {
name
}
}
}
}
}
}' --jq '.data.organization.team.repositories.nodes |
map({name: .name, default_branch: .defaultBranchRef.name})')
echo "repos=$REPOS" >> $GITHUB_OUTPUT
COUNT=$(echo "$REPOS" | jq 'length')
echo "count=$COUNT" >> $GITHUB_OUTPUT
echo "Found $COUNT repositories"
Key Features¶
- GraphQL query scopes to specific team
- Returns repository names and default branch
- Outputs JSON array for matrix strategy
Pagination Handling¶
GraphQL queries have a 100-item limit per request. Without pagination, repositories beyond the first 100 are silently ignored.
The 100-Repository Trap
If your team has 105 repositories and you query with first: 100, only 100 are returned.
The workflow completes successfully—but 5 repositories never receive updates.
This is a silent failure that's easy to miss.
Fail-Fast Approach¶
For teams unlikely to exceed 100 repositories, fail loudly when the limit is reached:
#!/bin/bash
# fetch-repos.sh - Fail-fast pagination guard
RESPONSE=$(gh api graphql -f query='
{
organization(login: "your-org") {
team(slug: "target-team") {
repositories(first: 100) {
totalCount
nodes {
name
defaultBranchRef {
name
}
}
}
}
}
}')
TOTAL=$(echo "$RESPONSE" | jq '.data.organization.team.repositories.totalCount')
RETURNED=$(echo "$RESPONSE" | jq '.data.organization.team.repositories.nodes | length')
if [ "$TOTAL" -gt "$RETURNED" ]; then
echo "::error::Repository count ($TOTAL) exceeds query limit ($RETURNED). Implement pagination."
exit 1
fi
REPOS=$(echo "$RESPONSE" | jq '.data.organization.team.repositories.nodes |
map({name: .name, default_branch: .defaultBranchRef.name})')
echo "repos=$REPOS" >> "$GITHUB_OUTPUT"
echo "count=$RETURNED" >> "$GITHUB_OUTPUT"
The totalCount field reveals how many repositories exist, even when nodes is limited. Compare them to detect truncation.
Full Pagination¶
For larger organizations, use the --paginate flag:
REPOS=$(gh api graphql --paginate -f query='
{
organization(login: "your-org") {
team(slug: "target-team") {
repositories(first: 100) {
pageInfo {
hasNextPage
endCursor
}
nodes {
name
defaultBranchRef {
name
}
}
}
}
}
}' --jq '.data.organization.team.repositories.nodes |
map({name: .name, default_branch: .defaultBranchRef.name})')
The --paginate flag automatically follows pageInfo.hasNextPage and aggregates results.
Choosing an Approach¶
| Team Size | Approach | Rationale |
|---|---|---|
| < 50 repos | Simple query | No risk of hitting limit |
| 50-100 repos | Fail-fast guard | Catch limit before it's a problem |
| > 100 repos | Full pagination | Required for completeness |
Related¶
- Stage 2: Distribution - Process discovered repositories
- Workflow Configuration - Triggers and scheduled runs
- Architecture - Three-stage workflow overview
References¶
- GitHub GraphQL API - Query syntax and explorer
- gh api graphql - CLI reference for GraphQL queries
- GraphQL Pagination - Official pagination guide