Working with Forks¶
When contributing to an API repository you don't own, you'll typically work on a fork — a personal copy of the canonical repo under your own GitHub organization or user account. APX is fork-aware and handles most of this automatically, but there are important limitations around releasing.
How Fork Detection Works¶
When you run apx init (or any command that needs org/repo defaults), APX inspects your git remotes:
origin— your fork (e.g.git@github.com:your-user/apis.git)upstream— the canonical repo (e.g.git@github.com:acme-corp/apis.git)
If both remotes exist and point to different organizations, APX assumes you're working on a fork and automatically uses the upstream org for all consumption paths. This ensures generated import paths, go_package options, and dependency references all point to the canonical repository — not your fork.
# Your remotes
origin → git@github.com:your-user/apis.git (your fork)
upstream → git@github.com:acme-corp/apis.git (canonical)
# APX detects:
# Org = acme-corp (from upstream, not your-user)
# Repo = apis
# UpstreamOrg = acme-corp
Setting Up Your Fork¶
# Fork the canonical repo on GitHub, then:
git clone git@github.com:<your-user>/apis.git
cd apis
git remote add upstream git@github.com:<canonical-org>/apis.git
# APX will now auto-detect the canonical org
apx init canonical --non-interactive
# → org: <canonical-org> (detected from upstream remote)
What Works on a Fork¶
All consumption and authoring workflows work correctly on forks:
| Workflow | Status | Details |
|---|---|---|
apx init | Works | Auto-detects canonical org from upstream remote |
apx lint | Works | Schema validation is local |
apx breaking | Works | Compatibility checks against local refs |
apx gen | Works | Code generation uses canonical import paths |
apx show | Works | Displays canonical identity correctly |
apx inspect identity | Works | Shows canonical go_package and module paths |
apx explain go-path | Works | Derives paths from canonical org/repo |
apx search | Works | Queries the catalog |
| Local development | Works | go.work overlays resolve to local code |
What Does NOT Work on a Fork¶
Releasing from a fork is not supported. Several operations require write access to the canonical repository, which fork contributors typically don't have:
1. apx release submit — Release Submission¶
apx release submit pushes a release branch (apx/release/<api-id>/<version>) to the canonical repo and opens a PR. Fork contributors cannot push branches to a repo they don't own.
2. apx release tag — Tag Creation¶
Release tags (e.g. proto/payments/ledger/v1/v1.0.0) are created on the canonical repo by post-merge CI. Fork contributors cannot create tags on a repo they don't own.
3. CI-Triggered Releasing¶
CI workflows on your fork run with your fork's credentials, not the canonical repo's. Any CI step that calls apx release submit will fail because the fork's GITHUB_TOKEN doesn't have write access to the upstream repo.
Recommended Fork Workflow¶
The correct pattern is to author on the fork, release from the canonical repo's CI:
┌─────────────────────────┐ ┌──────────────────────────┐
│ Your Fork │ │ Canonical Repo │
│ │ │ │
│ 1. Author schemas │ │ │
│ 2. apx lint │ │ │
│ 3. apx breaking │ │ │
│ 4. apx gen go │ │ │
│ 5. git push origin │ │ │
│ │ │ │
│ 6. Open PR ─────────────────── │ 7. CI validates PR │
│ │ │ 8. Reviewers approve │
│ │ │ 9. Merge to main │
│ │ │ 10. Post-merge CI: │
│ │ │ - apx release finalize │
│ │ │ - apx catalog generate │
│ │ │ - tag creation │
└─────────────────────────┘ └──────────────────────────┘
Steps 1–6 happen on your fork with your credentials. All consumption-side commands work because APX resolves the canonical org from your upstream remote.
Steps 7–10 happen on the canonical repo's CI, which has the GitHub App credentials to create tags, update the catalog, and finalize releases.
Example CI Configuration (Canonical Repo)¶
The canonical repo uses two workflows generated by apx init canonical:
PR validation (.github/workflows/ci.yml):
name: APX Schema CI
on:
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: infobloxopen/apx@main
- run: apx lint
- run: apx breaking --against origin/main
Post-merge catalog build & publish (.github/workflows/on-merge.yml):
name: APX On Merge
on:
push:
branches: [main]
permissions:
contents: read
packages: write
id-token: write
attestations: write
jobs:
catalog:
runs-on: ubuntu-latest
env:
IMAGE: ghcr.io/<org>/${{ github.event.repository.name }}-catalog
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.APX_APP_ID }}
private-key: ${{ secrets.APX_APP_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- uses: infobloxopen/apx@main
- run: apx lint
- run: apx catalog generate --output catalog/catalog.yaml
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: |
docker build \
--build-arg CREATED="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--build-arg REVISION="${{ github.sha }}" \
--build-arg SOURCE="https://github.com/${{ github.repository }}" \
--build-arg VERSION="${{ github.sha }}" \
-t "$IMAGE:latest" \
-t "$IMAGE:sha-${GITHUB_SHA::7}" \
catalog/
- id: push
run: |
docker push "$IMAGE:latest"
docker push "$IMAGE:sha-${GITHUB_SHA::7}"
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE:latest" | cut -d@ -f2)
echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"
- uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
- uses: anchore/sbom-action@v0
with:
image: ${{ env.IMAGE }}:latest
output-file: sbom.spdx.json
- uses: actions/attest-sbom@v2
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ steps.push.outputs.digest }}
sbom-path: sbom.spdx.json
push-to-registry: true
See CI Templates for details on these workflows.
Troubleshooting¶
APX uses my fork org instead of the canonical org¶
Ensure you have an upstream remote pointing to the canonical repo:
git remote -v
# Should show:
# origin git@github.com:<your-user>/apis.git (fetch)
# upstream git@github.com:<canonical-org>/apis.git (fetch)
# If upstream is missing:
git remote add upstream git@github.com:<canonical-org>/apis.git
apx release submit fails with "permission denied"¶
You're likely running the release from a fork. Releasing must happen from the canonical repo's CI after your PR is merged. See Recommended Fork Workflow above.
Import paths show my fork org in generated code¶
This means APX didn't detect the fork. Check that: 1. The upstream remote exists and points to the canonical repo 2. The upstream org is different from origin — APX only overrides when orgs differ 3. Run apx inspect identity to verify the resolved org