> ## Documentation Index
> Fetch the complete documentation index at: https://code.storage/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub Sync

> Mirror a GitHub repository through Code Storage with GitHub App sync, public mode, and Git LFS.

Code Storage can keep a repository in sync with GitHub. Your tools, automations, and users still use
the Code Storage remote.

Git Sync mirrors the default-namespace branches and tags. It does not mirror ephemeral refs.

Use GitHub Sync when you want to:

* Mirror a repository from GitHub
* Push to Code Storage and let Code Storage forward those writes to GitHub
* Continue to use Code Storage APIs, JWT-backed remotes, ephemeral branches, and webhooks on the
  mirrored repository

## Sync modes

Code Storage currently supports three Git Sync modes:

| Mode                                      | Best for                                                                  | Authentication                    |
| ----------------------------------------- | ------------------------------------------------------------------------- | --------------------------------- |
| GitHub App                                | Private GitHub repositories and webhook-driven sync                       | GitHub App installation token     |
| Public GitHub                             | Public GitHub repositories you want to pull from without credentials      | No GitHub credentials             |
| [Generic HTTPS Git](/docs/guides/generic-sync) | GitLab, Bitbucket, Gitea, Forgejo, Codeberg, SourceHut, and similar hosts | Stored username/password or token |

This page covers the two GitHub modes. For every other host, see
[Generic Sync](/docs/guides/generic-sync).

## GitHub App sync

The SDK has a direct setup flow for GitHub App sync. Create the repository with a GitHub base. Call
`pullUpstream()` when you must force a refresh.

<CodeGroup>
  ```typescript TypeScript theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  const repo = await store.createRepo({
    id: 'my-synced-repo',
    baseRepo: {
      owner: 'your-github-org',
      name: 'repository-name',
      defaultBranch: 'main',
    },
    defaultBranch: 'main',
  });

  await repo.pullUpstream();
  ```

  ```python Python theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  repo = await storage.create_repo(
      id="my-synced-repo",
      base_repo={
          "owner": "your-github-org",
          "name": "repository-name",
          "default_branch": "main",
      },
      default_branch="main",
  )

  await repo.pull_upstream()
  ```

  ```go Go theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  repo, err := client.CreateRepo(context.Background(), storage.CreateRepoOptions{
  	ID: "my-synced-repo",
  	BaseRepo: storage.GitHubBaseRepo{
  		Owner:         "your-github-org",
  		Name:          "repository-name",
  		DefaultBranch: "main",
  	},
  	DefaultBranch: "main",
  })

  err = repo.PullUpstream(context.Background(), storage.PullUpstreamOptions{})
  ```
</CodeGroup>

**How it works:**

* Code Storage links the repository to GitHub when you create it with
  [`baseRepo`](/docs/reference/sdk/create-repo)
* `pullUpstream()` gets the latest changes from GitHub
* All [SDK features](/docs/reference/sdk/create-repo) work with the synced content, such as diffs,
  commits, and file access
* Code Storage sets the provider to `"github"` when `baseRepo` contains `owner` and `name`

Code Storage treats GitHub as the source of truth for synced repositories. Code Storage sends all
pushes directly to GitHub. It then copies all changes back from GitHub.

During the initial sync, API calls for the repository return a `409 Conflict`:

```
ApiError: repository sync in progress. please retry shortly
  status: 409,
  statusText: 'Conflict',
  method: 'GET',
  body: { error: 'repository sync in progress. please retry shortly' }
```

Add retry logic for this response to your repository creation flow. Code Storage emits push webhook
events after the initial sync finishes.

After the initial sync, reads do not wait for later upstream syncs and may temporarily be stale. To
read the latest upstream content, wait for the
[`repo.sync.succeeded` webhook](/docs/guides/webhooks#reposyncsucceeded).

### Git LFS on GitHub App sync

GitHub App sync repositories support [Git LFS](/docs/guides/git-lfs) over the same Code Storage remote:

* **Downloads**: Code Storage serves an object that it already stores. If the object is absent, the
  LFS client gets it from GitHub. Code Storage copies the object in the background for the next
  request.
* **Uploads to a normal ref**: The client sends object bytes directly to GitHub. Code Storage does
  not store the upload.
* **Uploads to an ephemeral ref**: The object bytes stay in Code Storage and never reach GitHub.

<Warning>
  API promotion (`repo.createBranch()`) does not upload LFS objects to GitHub. An object that exists
  only on an ephemeral ref reaches GitHub as a pointer without its bytes when you promote the ref.
  Code Storage still serves the object, but a direct GitHub checkout of the promoted branch cannot
  download it. Push the LFS-tracked files through a normal ref to upload the objects to GitHub.
</Warning>

Public GitHub sync and [generic HTTPS Git sync](/docs/guides/generic-sync) do not support LFS.

## Public GitHub mode

If the upstream repository is public, you can skip GitHub App auth and create a synced repository in
public mode.

<CodeGroup>
  ```typescript TypeScript theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  const repo = await store.createRepo({
    id: 'public-upstream-repo',
    baseRepo: {
      owner: 'octocat',
      name: 'hello-world',
      defaultBranch: 'main',
      auth: {
        authType: 'public',
      },
    },
  });

  await repo.pullUpstream();
  ```

  ```python Python theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  repo = await storage.create_repo(
      id="public-upstream-repo",
      base_repo={
          "owner": "octocat",
          "name": "hello-world",
          "default_branch": "main",
          "auth": {
              "auth_type": "public",
          },
      },
  )

  await repo.pull_upstream()
  ```

  ```go Go theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  repo, err := client.CreateRepo(context.Background(), storage.CreateRepoOptions{
  	ID: "public-upstream-repo",
  	BaseRepo: types.GitHubBaseRepo{
  		Owner:         "octocat",
  		Name:          "hello-world",
  		DefaultBranch: "main",
  		Auth: types.GitHubBaseRepoAuth{
  			AuthType: types.GitHubBaseRepoAuthTypePublic,
  		},
  	},
  })

  err = repo.PullUpstream(context.Background(), storage.PullUpstreamOptions{})
  ```
</CodeGroup>

Public mode runs one sync or import. It does not keep a continuous sync. Use it for templates and
public repositories. Public mode has these limits:

* Code Storage does not subscribe to GitHub webhooks
* Code Storage does not keep a continuous, two-way sync with GitHub
* Code Storage does not automatically copy changes from GitHub
* Code Storage does not automatically send changes to GitHub
* The GitHub repository must remain public. If it becomes private, use authenticated mode before you
  sync it again

Use GitHub App mode when you need a continuous, two-way sync with GitHub.

### Public mode vs GitHub App sync

| Capability                     | Public mode (`authType: public`)        | GitHub App mode                                     |
| ------------------------------ | --------------------------------------- | --------------------------------------------------- |
| Sync model                     | One-time sync/import                    | Continuous sync                                     |
| Webhooks                       | None                                    | GitHub sends update events                          |
| GitHub to Code Storage updates | One pull                                | Code Storage receives webhook updates               |
| Code Storage to GitHub updates | No automatic updates                    | Code Storage forwards pushes                        |
| Git LFS                        | Not supported                           | Supported (download mirror + upload passthrough)    |
| Access requirements            | Public repo only, no GitHub credentials | GitHub App installation with repository permissions |

## Set up a GitHub App

Set up a GitHub App before you enable GitHub App sync.

<Steps>
  <Step title="Create the app">
    Open **Settings -> Developer settings -> GitHub Apps**. Create a new GitHub App.
  </Step>

  <Step title="Set permissions">
    Repository permissions:

    * Metadata: **Read** (required)
    * Contents: **Read** for one-way sync from GitHub, or **Read and write** for bidirectional sync
    * Workflows: **Read and write** (only if pushes will change files under `.github/workflows/`)

    <Note>
      Code Storage requests no fixed permission set when it creates an installation token. The
      connection uses the permissions that the installation grants. If a permission is absent, the
      related push fails. The setup can still succeed.
    </Note>

    Webhook events:

    * Push
    * Create
    * Pull Request (optional, if you want PR sync)

    To let Code Storage receive the events, use this webhook callback URL:

    ```
    https://[your-organization].code.storage/webhooks/github
    ```

    Or [use your own handler](#handle-webhooks-yourself).
  </Step>

  <Step title="Record credentials">
    Save these values for the Code Storage configuration:

    * GitHub App ID
    * Private Key
    * Webhook Secret
  </Step>
</Steps>

## Automatic sync with webhooks

GitHub sends webhook events when the repository changes. Handle the events in your service, or let
Code Storage handle them.

<div id="handle-webhooks-yourself" />

### Option A: handle webhooks yourself

To use your own webhook handler, set the callback URL to its endpoint. Process each GitHub event.
Call Code Storage as necessary.

```js theme={"theme":{"light":"github-light","dark":"min-dark"}}
// Your webhook handler
app.post('/github-webhook', async (req, res) => {
  // Verify GitHub webhook signature
  const signature = req.headers['x-hub-signature-256'];
  if (!verifyGitHubSignature(req.body, signature, webhookSecret)) {
    return res.status(401).send('Invalid signature');
  }

  // When you receive a push event, trigger Code Storage sync
  if (req.headers['x-github-event'] === 'push') {
    const repo = await store.findOne({
      id: mapGitHubRepoToStorageId(req.body.repository.full_name),
    });

    // Manually trigger a pull from GitHub
    await repo.pullUpstream();
  }
  res.status(200).send('OK');
});
```

Call [`repo.pullUpstream()`](/docs/reference/sdk/pull-upstream) from your handler to start a GitHub sync.
GitHub sends the `x-hub-signature-256` header. GitHub uses your GitHub App webhook secret to sign
the payload. Use the GitHub documentation to check this signature.

Code Storage also sends events for pushes and each sync stage. See [Webhooks](/docs/guides/webhooks) for
the event list, payloads, headers, and HMAC check.

### Option B: let Code Storage handle webhooks

If you do not want to run your own webhook handler:

1. In your GitHub App settings, set the webhook URL to
   `https://[your-organization].code.storage/webhooks/github`
2. Generate a webhook secret and save it.
3. In the Code Storage dashboard, open the **Integrations** tab.
4. Enter your webhook secret and save it.

Code Storage receives GitHub events and starts syncs automatically.

## How Git Sync behaves

After you configure Git Sync, these rules apply:

* `git clone`, `git fetch`, and `git pull` read from Code Storage
* `repo.pullUpstream()` and `POST /api/repos/{repo_name}/pull-upstream` start an asynchronous
  refresh from GitHub
* Code Storage forwards each `git push` to GitHub
* A successful push starts a background sync to keep the Code Storage nodes current

Your app can use Code Storage as its stable endpoint. Code Storage still copies changes to and from
GitHub.

Only pushes to the normal remote reach GitHub. Pushes to the
[`+ephemeral` remote](/docs/guides/ephemeral-branches) stay in Code Storage. A sync from GitHub copies
only `refs/heads/*` and `refs/tags/*`. Put machine state on ephemeral branches to keep it out of
GitHub. Machine state includes agent snapshots, preview builds, and scratch commits.

[Git LFS](/docs/guides/git-lfs) follows the same rule. An LFS upload to an ephemeral ref stays in Code
Storage and does not reach GitHub.

## Related reference pages

* [Create repository](/docs/reference/api/repositories/create-repo)
* [Pull from upstream](/docs/reference/api/repositories/pull-upstream)
* [createRepo()](/docs/reference/sdk/create-repo)
* [pullUpstream()](/docs/reference/sdk/pull-upstream)
* [Git LFS](/docs/guides/git-lfs)
* [Webhooks](/docs/guides/webhooks)
* [Generic Sync](/docs/guides/generic-sync)
* [Repository Forks](/docs/guides/forking)

## Support

For help, email [jacob@pierre.co](mailto:jacob@pierre.co).
