# Migrate from GitHub

Codebahn uses GitHub Actions workflow syntax, supports the same SSH keys, and uses standard Git. Import carries code, issues, pull requests, labels, milestones, and releases in one pass.

## Prerequisites

- A Codebahn account with an organization (see [getting started](/docs/getting-started/))
- Access to the GitHub repos you want to migrate
- Git installed locally (for pointing your clone at the new remote afterward)

You can import through the Codebahn web UI or the [`codebahn` CLI](/docs/cli/). The web UI offers two auth methods: the Codebahn GitHub App (no tokens to copy) or a personal access token.

## Option A: GitHub App

The fastest path. Codebahn connects to your GitHub account directly, lists your repos, and lets you pick which ones to import.

1. Log in to Codebahn

2. Click **+** > **Import repository** > **GitHub**

3. On the **Connect GitHub** tab, click **Connect GitHub account**

4. Authorize the Codebahn app on GitHub. If this is your first time, install it on your account or organization and grant access to the repos you want to import

5. Back on Codebahn, select the repos to import. Use the filter to narrow a long list

6. Choose which metadata to bring along: issues, pull requests, labels, milestones, releases

7. Choose an owner (your account or an organization)

8. Click **Import selected**

The connection lasts 30 minutes. A countdown shows the remaining time. When it expires, click **Connect GitHub account** again to reconnect. Reconnecting is instant if the app is still installed.

Codebahn does not keep access to your GitHub after the connection expires. No tokens or credentials are stored. (If you check **Keep in sync** to create a [mirror](/docs/mirror-github-repos/), the installation ID is stored so syncs can mint tokens on demand. It is not a secret, and you can revoke access by uninstalling the app on GitHub.)

:::note[Switching GitHub accounts]
If you installed the Codebahn app on both your personal account and an organization, click **Switch account** to import from a different one. This takes you to GitHub's installation page where you pick the account. If the app is already installed there, GitHub shows the settings page instead of redirecting back. To get back to Codebahn, toggle any setting and toggle it back (for example, switch repository access from "All repositories" to "Only select repositories" and back), then click **Save**.
:::

:::tip[Permissions]
The app requests read-only access to contents, issues, pull requests, and metadata. If you skip a permission during install, the corresponding checkbox is disabled on the import page. To update permissions, click **Configure** next to the connected account and adjust on GitHub.
:::

## Option B: Personal access token

If you prefer not to install the app, or need to import from a GitHub account where you cannot install apps, use a token.

### Create a GitHub personal access token

A fine-grained token scoped to specific repos is the safest option.

1. Open GitHub > **Settings** > **Developer settings** > **Personal access tokens** > **Fine-grained tokens**

2. Click **Generate new token**

3. Under **Resource owner**, select the GitHub org that owns the repos

4. Set a short expiration. Seven days is enough

5. Under **Repository access**, select **Only select repositories** and pick the ones you are migrating

6. Under **Permissions**, grant what you need:

| What you are importing | Required permission |
|---|---|
| Code | Contents: Read-only |
| Issues, labels, milestones | Issues: Read-only |
| Pull requests | Pull requests: Read-only |
| Releases | Contents: Read-only |

Fine-grained tokens always require **Metadata: Read-only**. It is added automatically when you select any other permission.

7. Click **Generate token** and copy the value

If your org enforces token approval, an owner must approve it under **Settings > Personal access tokens > Pending requests** before it works.

:::note[Classic tokens]
Classic tokens with the `repo` scope also work. A fine-grained token is preferred because it limits access to specific repos.
:::

### Import a repository

1. Log in to Codebahn

2. Click **+** > **Import repository** > **GitHub**

3. Switch to the **Personal access token** tab

4. In **Clone URL**, paste the HTTPS URL of your repo, e.g. `https://github.com/your-org/your-repo.git`. SSH URLs are converted to HTTPS automatically

5. Paste your token in the **Access token** field

6. Leave **This repository will be a mirror** unchecked for a full one-time import. To keep a live, syncing copy instead, see [Mirror a GitHub repository](/docs/mirror-github-repos/)

7. Select what to import: issues, pull requests, labels, milestones, releases

8. Choose your org as the owner and confirm the repo name

9. Click **Migrate repository**

After it completes, the Codebahn repo is independent from GitHub.

:::caution[Access control is not migrated]
Import brings code and issue history, not GitHub teams or permissions. Re-create your access control in Codebahn after import: invite your team and assign team membership.
:::

## Option C: CLI

The [`codebahn` CLI](/docs/cli/) can import a single repo or an entire GitHub organization from your terminal. [Install the CLI](/docs/cli/#install) first.

### Preview available repos

List which repos are accessible before importing:

```bash
codebahn repo list-remote github.com/your-org
```

This opens your browser to authorize the Codebahn GitHub App, then prints a table of repositories with their visibility and description. Use `--include` and `--exclude` with glob patterns to filter the list.

### Single repo

```bash
codebahn repo import github.com/your-org/your-repo
```

Without `--source-token`, the CLI authenticates through the Codebahn GitHub App device flow (opens your browser, no token to manage). To use a personal access token instead:

```bash
codebahn repo import github.com/your-org/your-repo --source-token $GITHUB_TOKEN
```

By default, the CLI imports issues, pull requests, labels, milestones, and releases. To skip specific metadata:

```bash
codebahn repo import github.com/your-org/your-repo --no-issues --no-prs
```

### Entire organization

```bash
codebahn repo import github.com/your-org --owner my-codebahn-org -y
```

Use `--include` and `--exclude` to select repos:

```bash
codebahn repo import github.com/your-org --include "backend-*" --exclude "deprecated-*" -y
```

For private repos and org-level operations, pass `--source-token` or set `GITHUB_TOKEN`. Without a token, the CLI uses the GitHub App device flow.

## After import

Point your local clone at Codebahn:

```bash
git remote set-url origin git@codebahn.net:your-org/your-repo.git
```

Add your SSH key first under **Settings > SSH / GPG Keys** if you haven't.

## CI workflows

Your `.github/workflows/` files run on Codebahn. Actions resolve from GitHub by default, so `uses: actions/checkout@v7` works as-is.

`runs-on: ubuntu-latest` routes to the [hosted EU runners](/docs/ci-runners/). You can also use `codebahn-small`, `codebahn-medium`, or `codebahn-large` for specific runner sizes. A different label (e.g. `ubuntu-22.04`) needs updating.

Codebahn's default runner (`ubuntu-latest`) has 3 GB of usable memory, less than GitHub's 8 GB (for private repos). Most workflows run fine. If a build that worked on GitHub is killed without a clear error, it likely hit the memory limit. Switch to `codebahn-medium` for 7 GB (2x [cost factor](/docs/ci-runners/#runner-sizes)).

What won't run: GitHub-specific actions that call the GitHub API, macOS and Windows jobs, OIDC federation, code scanning, and GitHub Pages. See [CI compatibility](/docs/ci-runners/#compatibility) for the full list. GitLab-style `.gitlab-ci.yml` is also ignored; rewrite it to Actions.

Codebahn also reads workflows from `.codebahn/workflows/`, `.forgejo/workflows/`, and `.gitea/workflows/`. See [workflow directories](/docs/workflow-directories/) for precedence.

### Auto-cancellation

Pushing a new commit to a branch with a running workflow cancels the in-progress run. GitHub does not do this by default. To let all runs finish, add:

```yaml
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: false
```

## Dependency updates

GitHub's Dependabot does not run on Codebahn. Your `.github/dependabot.yml` file is ignored.

Use [Renovate](/docs/dependency-management/) instead. It runs as a scheduled CI workflow on Codebahn's runners and opens pull requests the same way Dependabot does. The guide includes a field-by-field mapping from `dependabot.yml` to `renovate.json`.

## Migrate several repos

**Web UI (GitHub App):** select multiple repos and import them all at once (up to 100 per batch).

**CLI:** import an entire org in one command. See [Option C: CLI](#option-c-cli) above.

**API:** with the token flow, script imports via the [migration API](https://forgejo.org/docs/latest/developer/api-usage/).

### Manual fallback

If none of these fit your case, clone and push by hand:

```bash
git clone --bare https://github.com/your-org/your-repo.git
cd your-repo.git
git push --mirror git@codebahn.net:your-org/your-repo.git
```

This moves code and branches only. Issues, pull requests, and releases are not included.

## Troubleshooting

**"Only http(s):// or git:// locations can be used"**
You pasted an SSH URL. Use the HTTPS URL: `https://github.com/your-org/your-repo.git`

**401 or "Authentication failed"**
The token is expired, revoked, or missing permissions. Generate a new one with at least Contents: Read-only.

**404 or "Not found"**
The repo is private and the token lacks access, or an org owner hasn't approved the token yet.

**Issues or pull requests missing after import**
The token needs Issues: Read-only and Pull requests: Read-only. Re-run with a new token.

**"Could not list repositories. The installation may have been removed."**
The Codebahn app was uninstalled from your GitHub account. Click **Connect GitHub account** to re-install it.

**GitHub App connection expired**
Connections last 30 minutes. Click **Connect GitHub account** to reconnect. Reconnecting is instant if the app is still installed.

## You can leave any time

What you import, you can take back out. [Org Settings > Export](/docs/export/) produces a `tar.gz` with every repo's full Git history plus issues, pull requests, labels, milestones, and releases. Portable by design.
