Files
ai-workflow-course/modules/08-remotes-and-hosting/README.md
T
claude a3ff56e570
Sync course wiki / sync-wiki (push) Has been cancelled
Update learner working-dir path to ~/ai-workflow-course after rename (#73)
Co-authored-by: claude <claude@jpaul.io>
Co-committed-by: claude <claude@jpaul.io>
2026-06-22 18:54:23 -04:00

494 lines
30 KiB
Markdown

# Module 8 — Remotes and Hosting: GitHub, the Alternatives, and Owning Your Repo
> **One repo on one laptop is one spilled coffee away from gone.** A remote gets your history
> off your machine and somewhere durable — and because every clone carries the full history, a
> working team backs itself up just by working.
---
## Prerequisites
- **Module 2** — you have a Git repo (`tasks-app`) with real commits, and you understand commits as
checkpoints and the repo as durable memory. This module gets that history *off the one disk it
lives on*.
- **Module 5** — you committed your agentic tool's instructions file into the repo. A remote is what
finally makes that config *shared*: push it once and every teammate (and every agent) pulls the
same setup.
- **Module 6** — you can work on branches. Pushing is per-branch, so knowing what a branch is matters
here.
Helpful but not required: **Module 7** (worktrees). Everything below works the same whether you have
one working directory or several.
---
## Learning objectives
By the end of this module you can:
1. Explain what a remote *is* — a named pointer to another copy of the same repo — and why "it's just
another copy" is the whole reason hosting is provider-neutral.
2. Add a remote, push your history to it, and pull changes back, on any forge, with the same commands.
3. Recover from the three failure modes that bite everyone on first push: authentication, a
non-empty remote, and a branch-name mismatch.
4. Choose a host deliberately — hosted vs. self-hosted — using a current, dated comparison instead of
defaulting to GitHub by reflex.
5. State precisely where "pushing to a remote" is and isn't a backup, and how a normal team workflow
accidentally satisfies most of the 3-2-1 rule.
---
## Key concepts
### A remote is just another copy
A **remote** is a named reference to *another copy of this same repository*, usually somewhere you
can reach over the network. That's it. `origin` is not a
GitHub concept, a GitLab concept, or a Gitea concept — it's a Git concept, and the copy it points at
is a full, equal Git repo that happens to live on a server.
This is the fact the entire rest of the module rests on, so sit with it: **because a remote is just
another copy, the commands you use to talk to it are identical no matter who hosts it.** `git push`
to GitHub is byte-for-byte the same operation as `git push` to a **forge** (a Git hosting platform —
GitHub, GitLab, Gitea, Forgejo, and the like) you run yourself in a locked-down rack. The provider is
a logistics decision — uptime, price, who can see it, where the servers sit — not a Git decision. We
lean on GitHub as the worked example below *only* because it's
the one you're most likely to hit first, not because the mechanics change anywhere else.
The local-to-remote vocabulary is small:
```bash
git remote add origin <URL> # register a remote named "origin" at this URL (once per repo)
git remote -v # list remotes and their URLs
git push -u origin main # send your "main" branch up; -u links local main to origin/main
git push # after the first -u push, this is all you need
git pull # fetch the remote's changes AND merge them into your branch
git fetch # fetch the remote's changes WITHOUT merging (look before you leap)
git clone <URL> # make a brand-new local copy from a remote (history and all)
```
`origin` is just the conventional name for "the place I push to." You can have more than one remote
(a personal fork *and* the team's repo, say), and they can live on different hosts entirely — one on
a SaaS forge, one on a box in your closet. Git doesn't care.
### Getting a remote: you create the empty repo first
The one piece the commands above assume is that a remote repo *exists* to push into. On every host
the shape is the same:
1. In the host's web UI (or its CLI/API), create a **new, empty** repository. Give it a name; do
**not** let it add a README, license, or `.gitignore` — you want it empty so your local history
is the first thing in it.
2. Copy the URL it gives you. You'll see two flavours:
- **HTTPS** — `https://host/you/tasks-app.git`. Authenticates with a username + a personal access
token (not your account password — password auth over Git is gone on essentially every modern
host).
- **SSH** — `git@host:you/tasks-app.git`. Authenticates with an SSH key you've added to your
account. More setup once, less friction forever.
3. Point your local repo at it and push:
```bash
cd ~/ai-workflow-course/tasks-app
git remote add origin <URL-you-copied>
git push -u origin main
```
That `-u` (short for `--set-upstream`) is worth understanding, not just copying: it records that your
local `main` *tracks* `origin/main`. After it, `git status` will tell you things like "your branch is
ahead of origin/main by 2 commits" — the ahead/behind report you met in Module 2, now meaningful
because there's finally a remote to be ahead *of*. And `git push` / `git pull` with no arguments know
where to go.
### The three failure modes of a first push
Everyone hits at least one of these. Recognizing them by their error text saves an afternoon.
**1. Authentication fails.** You push and get `Authentication failed`, `Permission denied
(publickey)`, or a `403`. Two different causes hide behind that wall, and they have different fixes.
The common one is *no usable credential at all* — you tried an account password (dead on every modern
host) or never set up a token / SSH key. The sneakier one is a credential that *exists but lacks the
right scope*: a token authenticates fine and then the push is refused with `403` because the token was
never granted write access to repositories. They look alike but you fix them differently — create a
credential vs. *edit the existing token's scopes* (don't regenerate it). For the no-credential case:
for HTTPS, generate a personal access token in the host's settings and use it as your password when
prompted; for SSH, generate a key (`ssh-keygen`) and paste the public half into the host's SSH-keys
settings. This is host-specific UI but the *concept* is identical everywhere — the callout below walks
the shape of getting one.
> ### Getting a credential (the shape)
>
> The exact menu names and scope labels drift per host, so treat these as the *shape*, not gospel
> (**Verify-before-publish** the specific UI wording for your forge):
>
> - **Scope is the gotcha — check it first.** In the host's **Settings → developer / access tokens →
> create token**, you must grant the token write access to repositories: usually a scope literally
> named `repo`, or a "read **and write**" toggle on the repositories resource. A token created
> *without* it authenticates and then `403`s on push — it looks like an auth failure, but the fix is
> to **edit the token's scopes**, not to delete and recreate it.
> - **The token is shown once.** Hosts reveal the value a single time at creation. Copy it the moment
> it appears; if you lose it you create a new one rather than recover the old.
> - **Pasting it is invisible, and only happens once.** When Git prompts for your "password," paste
> the token — most terminals show *nothing* as you paste a secret, which is normal, not a failure.
> A **credential helper** (`git config --global credential.helper …`, e.g. `store`, `cache`, or your
> OS keychain) remembers it after the first success so you aren't pasting it on every push.
> - **SSH is the alternative.** A key you've added to the host skips passwords entirely: more setup
> once, no token to scope or cache afterward.
**2. The remote isn't empty (non-fast-forward).** You let the host create the repo *with* a README,
then push, and get `! [rejected] ... (fetch first)` or `non-fast-forward`. The remote has a commit
your local history doesn't, so Git refuses to overwrite it. The simple fix is to **recreate the remote
empty** and push again. (The alternative you'll see online — `git pull --rebase origin main`, then
push — replays your commits on top of the remote's, but `rebase` is an advanced, history-rewriting
operation this course doesn't teach as a step here, so prefer the empty-remote fix for now. And note
that plain `git pull` won't rescue you against an auto-README remote — it refuses to merge unrelated
histories.) This is the same "someone else pushed before me" situation you'll hit constantly once
you're collaborating — Module 11 — except here the "someone else" was the host's auto-generated README.
**3. Branch-name mismatch.** Your local default branch is `master` but the host expects `main` (or
vice versa). `git push -u origin main` then errors with `src refspec main does not match any`. Fix:
check what you actually have with `git branch`, and either push the branch you have
(`git push -u origin master`) or rename it first (`git branch -m main`). If you initialized with
`git init -b main` back in Module 2, you're already on `main` and this one won't bite you here — but
it's the classic wall for any repo that started life on `master`, so it's worth recognizing.
### Pull, fetch, and the everyday loop
Once the remote exists, day-to-day work adds two moves to the Module 2 loop:
- **`git pull`** before you start, to get whatever the remote gained since you last looked. It's a
`fetch` (download) plus a merge into your current branch in one step.
- **`git push`** after you've committed, to send your new checkpoints up.
When you want to *see* what the remote has before you let it touch your working files, use
**`git fetch`** instead — it downloads the remote's commits into `origin/main` but leaves your branch
untouched, so you can `git log main..origin/main` to read exactly what's incoming before merging.
That "look before you leap" habit matters more the moment other contributors — human or agent — are
pushing to the same place.
### Choosing a host: the comparison
GitHub is the titan. It is by a wide margin the largest forge, it's where most open source lives, and
it's the one AI tooling integrates with *first* — when a new coding agent or MCP server ships, GitHub
support is usually in the first release and everything else trails. That makes it the sane default for
most people, and it's why this module uses it as the worked example. But "default" is not "only," and
for a team with on-prem, air-gapped, or data-control requirements — a real and common constraint for
this audience — it may be the wrong default. The genuine choice is between **hosted** (someone runs
the forge; you just use it) and **self-hosted** (you run the forge on your own infrastructure).
> ### Hosting comparison — as of 2026-06-22
>
> Pricing and feature claims drift fast. Everything in these two tables was checked on the date above
> and must be re-verified before you rely on it — see the **Verify-before-publish** checklist at the
> end. List prices are per-user/month at the entry paid tier, billed annually, in USD; promotional
> and volume discounts are common and not shown.
**Hosted forges (someone else runs it):**
| Platform | Pricing (entry → paid) | Built-in CI/CD | AI-tooling integration | Ease of operation |
|---|---|---|---|---|
| **GitHub** | Free; Team ~$4/user; Enterprise ~$21/user | GitHub Actions, built in (Free tier includes a monthly minutes allowance for private repos; unlimited for public) | **Deepest.** Most agents, MCP servers, and AI reviewers target GitHub first | Zero ops — pure SaaS |
| **GitLab** (SaaS) | Free (capped users/namespace, small CI allowance); Premium ~$29/user; Ultimate ~$99/user | GitLab CI/CD — among the most mature, deeply integrated pipelines | Strong; first-party AI assistant plus growing agent support | Zero ops as SaaS; also self-hostable (see below) |
| **Bitbucket** (Atlassian) | Free (≤5 users); Standard ~$3.65/user; Premium ~$7.25/user | Pipelines, built in (small free monthly build-minute allowance) | Growing; tightest value is deep Jira/Atlassian tie-in | Zero ops as SaaS; Data Center edition self-hostable (enterprise pricing) |
| **Azure DevOps** | First 5 users free; Basic ~$6/user beyond; pipelines ~$40/parallel job after a free job | Azure Pipelines, built in (one free parallel job + monthly minutes) | Good within the Microsoft ecosystem; Copilot integration | Zero ops as SaaS; Azure DevOps Server self-hostable |
| **Codeberg** | Free (FOSS projects only; soft repo/storage caps) | Forgejo Actions (it runs Forgejo) | Via API/MCP; not a first-tier agent target | Zero ops; nonprofit-run, no commercial/closed-source hosting |
| **SourceHut** | Paid to host: ~$5 / $10 / $15 (all tiers buy the *same* service — "pay what's fair"); reduced ~$2 rate / financial aid if the full price is a hardship; free to *contribute* | builds.sr.ht, built in | Minimal first-class AI tooling; reachable via API | Zero ops as SaaS; fully self-hostable (it's open source) |
**Self-hostable open-source forges (you run it):**
| Forge | License / cost | Built-in CI/CD | AI-tooling integration | Ease of operation |
|---|---|---|---|---|
| **Forgejo** | Free, open source (you pay infra + ops) | Forgejo Actions — runs GitHub-Actions-compatible workflow YAML | Full REST API; community MCP servers; agents work over git + API | **Easiest.** Single Go binary, runs on a tiny VPS (~256 MB RAM). Community/nonprofit governed |
| **Gitea** | Free, open source | Gitea Actions (GitHub-Actions-compatible YAML) | Full REST API; community MCP servers | Single Go binary, same light footprint as Forgejo; company-backed |
| **GitLab CE** | Free, open source | Full GitLab CI/CD + container registry + more, in one install | Same first-party AI direction as GitLab SaaS, self-hosted | **Heaviest.** Wants ~8 GB+ RAM (Postgres/Redis/Sidekiq/Gitaly); upgrades can't skip versions |
| **Gogs** | Free, open source | None built in | API only | Lightest of all; single binary, runs on a Raspberry Pi. Slower development; no CI |
| **OneDev** | Free, open source | Built-in CI/CD configured in the **UI** (little/no YAML) + Kanban + packages | API; less common as an agent target | Single deployment; all-in-one but a smaller ecosystem |
Two things to read out of those tables rather than memorize the numbers:
- **GitLab spans both camps.** It's a hosted SaaS *and* a self-hostable Community Edition from the
same project — useful if you want SaaS now and the *option* to bring it in-house later without
changing tools.
- **"Self-hosted" trades a per-user bill for an ops bill.** The license is free; your cost is the
server, the upgrades, the backups, and the on-call. Forgejo/Gitea make that bill small (a single
binary on a cheap box). GitLab CE makes it real (a stack to feed and water). That trade is the
whole decision.
### The self-hosted-forge track (optional)
If you're in the air-gapped/on-prem audience, you can run this module's lab against a forge you stand
up yourself instead of a SaaS account. The teaching point is precisely that **nothing changes** — you
create an empty repo on your forge, copy its URL, `git remote add origin <URL>`, and `git push`. The
lab below flags exactly where the only difference is (the URL and how you authenticate to your own
box). Standing the forge up is its own exercise — Forgejo or Gitea is a single binary and the fastest
path; the *git* half is identical to the hosted track.
### Backup thesis, part one: distribution is the backup
Module 2 left you with a sharp limitation: everything lived on one disk. Drop the laptop in a lake and
the repo, history and all, is gone. A single local repo gives you *recovery* (move between
checkpoints) but not *backup* (a copy that survives the disk dying).
Pushing to a remote is what closes that gap, and Git's design makes the win bigger than it looks.
Recall the standard **3-2-1 backup rule**: keep **3** copies of your data, on **2** different media,
with **1** offsite. Now look at what a normal team doing normal work ends up with, without anyone
"doing backups":
- Your laptop has a full copy — **complete history**, not just current files.
- The remote has a full copy — **offsite**, on someone else's hardware (or your other box).
- Every teammate who has cloned the repo has *another* full copy, each with the entire history,
because **clone copies everything**, not a snapshot.
A four-person team that pushes to one remote is sitting on five-plus complete, independent copies of
the entire project history across multiple locations and machines. They didn't run a backup tool.
They just worked. That's the quiet superpower of a *distributed* version control system: distribution
*is* the redundancy. The 3-2-1 rule, which most ops shops fight to satisfy deliberately, falls out of
a forge and a working team almost for free.
Be precise about the division of labor, because the course is honest about where analogies stop:
- **Recovery power comes from commits (Module 2, and Module 12 for the harder cases).** That's your
point-in-time restore — go back to any checkpoint.
- **Backup power comes from remotes and distribution (this module).** That's your offsite,
redundant, survives-the-disk copy.
You need both. Commits without a remote survive a mistake but not a dead drive. A remote without good
commits survives a dead drive but gives you a junk drawer to restore from. Module 12 picks up the
*recovery* half in full and is just as honest about what Git is **not** a backup for — your database,
your secrets, your uncommitted work, your large binaries. We'll hold that thought there.
---
## The AI angle
A remote isn't only about durability — it's the substrate the AI parts of this course run on.
- **Most AI tooling integrates with the forge first, not your laptop.** AI reviewers, issue-to-PR
agents, and the CI that catches code which merely *looks* right (Modules 10, 14, and Unit 5) all
operate on the *remote* repo through its API and web UI. Until your history is pushed, none of that
machinery has anything to act on. A remote is the precondition for every agent-in-the-loop module
that follows.
- **GitHub's "integrates first" status is a real, current bias — name it, then decide.** Because the
largest forge is where AI tooling lands first, picking a less-common host or self-hosting can mean
thinner first-class agent support and more wiring-it-yourself over the API. That's a legitimate cost
to weigh against control and data-residency — *not* a reason to abandon the choice. The git
mechanics are identical everywhere; it's the AI ecosystem maturity that varies, and that gap is the
thing to check (it narrows constantly).
- **The committed AI config from Module 5 only pays off once it's pushed.** Locally, your agent's
instructions file just configures *your* agent. Pushed to the remote, it configures *everyone's* —
every teammate who clones, and every automated agent that later operates on the repo, inherits the
same conventions instead of each drifting into a private setup. The remote is what turns "my AI
config" into "the project's AI config."
- **A remote is an agent's recovery insurance.** When you hand an agent a branch and let it run
(Module 6, and Unit 5 at full autonomy), a pushed branch means its work survives a crashed session,
a wiped worktree, or a machine that dies mid-run. Push early; an agent's output that only exists in
one uncommitted, unpushed working directory is the most fragile state in this whole course.
---
## Hands-on lab
**Lab language:** shell (Git commands), plus one short provided shell script. Runs on macOS, Linux,
WSL, or Git Bash on Windows. Continues the `tasks-app` repo from Module 2.
**You'll need:**
- Your `tasks-app` Git repo from Module 2 (with several commits and a `.gitignore`).
- An account on a Git host. **Hosted track:** GitHub is the worked default, but GitLab, Bitbucket,
Codeberg, or any forge works with the identical commands. **Self-hosted track:** a Forgejo/Gitea
(or other) instance you can reach, and an account on it.
- The ability to authenticate to that host — a personal access token (for HTTPS) or an SSH key added
to your account. Set this up first; failure mode #1 above is the most common first-push wall.
- Your AI assistant (still the way you've used it — this lab is about the remote, not the editor).
### Part A — Create the empty remote and push
1. On your host's web UI, create a **new, empty** repository named `tasks-app`. Do **not** add a
README, license, or `.gitignore` — leave it empty so your local history goes in clean. Copy the URL
it shows you (HTTPS or SSH).
> **Self-hosted track:** identical step, on your own forge's UI. The only thing that differs from
> the hosted track is the URL (your forge's hostname) and how you authenticate to your box.
> Everything from here on is the same commands.
2. Point your repo at the remote and push:
```bash
cd ~/ai-workflow-course/tasks-app
git remote -v # probably empty — no remote yet
git remote add origin <URL> # paste the URL you copied
git remote -v # now origin shows, for fetch and push
git push -u origin main # send main up and link it
```
If `push` errors, match it to the three failure modes above: `Authentication failed` / `Permission
denied` → token or SSH key (#1); `non-fast-forward` / `fetch first` → the remote wasn't empty (#2);
`src refspec main does not match` → branch-name mismatch, check `git branch` (#3). Fix and re-push.
3. Confirm the offsite copy exists: refresh the host's web page for the repo. Your files and your full
commit history from Module 2 are now sitting on hardware that is not your laptop. **That is the
backup half the course promised.**
### Part B — Prove distribution is redundancy
You're going to demonstrate the 3-2-1 claim with your own eyes: that a clone is a *complete,
independent* copy, history and all — not a snapshot.
4. Make a change locally, commit it, and push it (with the AI if you like — e.g. ask for a `version`
command that prints the app version):
```bash
# apply the change, then:
git add .
git commit -m "Add version command"
git push # no args needed now, thanks to -u earlier
```
5. Now clone the remote into a *separate* directory, as if you were a teammate on a fresh machine:
```bash
cd ~/ai-workflow-course
git clone <URL> tasks-app-teammate
cd tasks-app-teammate
git log --oneline # the ENTIRE history is here — every commit, not just the latest
```
Compare the commit count to your original repo (`git log --oneline | wc -l` in each). They match.
The clone didn't get "the current files" — it got the whole project's memory. That's the property
that makes a working team into an accidental backup system.
6. Run the provided check from this module's `lab/` to make the point mechanically:
```bash
# from your original repo:
bash ~/ai-workflow-course/tasks-app/verify-backup.sh # (copied from lab/verify-backup.sh)
```
The script confirms (a) you have a remote configured, (b) your local branch is fully pushed
(nothing stranded only on your disk), and (c) a fresh clone of the remote carries the exact same
commit count as your local repo — i.e. the offsite copy is complete, not partial. Read its output;
the green line is your evidence that the backup is real.
> On the **HTTPS + token** path with a *private* repo, the clone check (c) needs your credential
> helper to have cached the token from your earlier push — otherwise it can't authenticate to clone.
> The script won't hang waiting for a prompt (it disables interactive credential prompts); it just
> reports a `NOTE` that it couldn't clone, and the push checks above still stand. SSH and public
> repos clone with no credential at all.
### Part C — The everyday loop
7. Edit the README in your *teammate* clone, commit, and push from there:
```bash
cd ~/ai-workflow-course/tasks-app-teammate
# edit README.md, then:
git add . && git commit -m "Note the remote in the README"
git push
```
8. Back in your *original* repo, pull it down:
```bash
cd ~/ai-workflow-course/tasks-app
git fetch # download the new commit, but don't merge yet
git log main..origin/main # SEE exactly what's incoming before you take it
git pull # now merge it into your local main
git log --oneline # the teammate's commit is now here too
```
That fetch-then-look-then-pull rhythm is the habit to keep: you saw what was coming before you let
it touch your files. You've now pushed *and* pulled across two independent copies through one
remote — the complete remotes mechanic.
### Part D (optional) — A second remote
9. Add a *second* remote (a personal fork on another host, or even a bare repo on a USB drive or a
box on your LAN) and push to it too:
```bash
git remote add backup <SECOND-URL>
git push backup main
git remote -v # two remotes now: origin and backup
```
You now literally have the 3-2-1 rule satisfied by hand: your laptop, `origin`, and `backup` — three
copies, more than one location. Nothing about Git stopped you from pointing at as many copies as you
want.
---
## Where it breaks
The honest limits — the backup analogy especially needs them.
- **A remote backs up what you *pushed*, nothing else.** Uncommitted edits, untracked files, and
anything `.gitignore` excludes (like `tasks.json` runtime state) never leave your laptop. "I pushed"
is not "everything is safe" — it's "every *committed and pushed* change is safe." The defense is the
Module 2 habit: commit often, and now, push often too.
- **Git is not a backup for non-Git things.** Your database, your secrets (which shouldn't be in the
repo anyway — Module 17), large binaries, and build artifacts are not covered by pushing code. The
3-2-1-by-accident win applies to your *versioned source*, full stop. Module 12 is blunt about this.
- **One remote is one vendor.** Distribution across a team is great redundancy against *disk* failure;
it's weaker against *account* failure. If your whole team only ever pushes to one host and that
account is suspended, locked, or the provider has an outage, your offsite copy is temporarily out of
reach (your local clones are fine). Part D's second remote, or a periodic clone to storage you
control, is the answer for anyone who needs it — and it's the on-ramp to the self-hosting argument.
- **"GitHub integrates first" is true today and a moving target.** Don't treat the AI-ecosystem gap
between hosts as permanent; it's exactly the kind of claim that ages. Re-check it for your tooling
before you let it decide your host.
- **The comparison tables are a snapshot, not a fact of nature.** Every price and tier above was true
on 2026-06-22 and will drift. Use them to learn the *dimensions* that matter (per-user cost vs. ops
cost, built-in CI or not, footprint, AI-ecosystem maturity), then check current numbers yourself.
---
## Check for understanding
**You're done when:**
- Your `tasks-app` exists on a remote, and `git remote -v` plus the host's web UI both confirm it.
- You have pushed at least one commit and pulled at least one commit back, across two copies of the
repo through one remote.
- `verify-backup.sh` reports a clean, fully-pushed state and a clone whose commit count matches your
local repo's — you've *seen* that the offsite copy is complete.
- You can explain, in your own words, why a four-person team pushing to one remote roughly satisfies
3-2-1 without running a backup tool — and name two things that win does *not* cover.
- You can state why the choice of host is a logistics decision, not a Git one, and name at least one
hosted alternative to GitHub and one self-hostable forge.
When pushing feels like the natural end of "commit" and you trust that your history is no longer
trapped on one disk, you have the *backup* half of the backup-and-recovery thread. Module 9 starts
using the remote for more than storage — issues, the task layer where humans and agents pick up
work — and Module 12 returns to finish the *recovery* half.
---
## Verify-before-publish
This module makes dated pricing and feature claims that drift. Re-check each before relying on the
tables, and update the "as of" date when you do.
- [ ] **GitHub** tiers and prices — Free / Team / Enterprise per-user/month, and the Free-tier CI
minutes allowance for private repos.
- [ ] **GitLab** tiers — Free (user/namespace caps, CI allowance), Premium, Ultimate per-user/month,
and the SaaS-vs-self-managed price split.
- [ ] **Bitbucket** tiers — Free user cap, Standard (~$3.65), Premium (~$7.25) per-user/month, and
free build-minute allowance. (Reconciled against Atlassian's own pricing page on 2026-06-22;
stale third-party listings still quote ~$2/$5 — trust Atlassian's page, and re-confirm.)
- [ ] **Azure DevOps** — free-user count, Basic per-user/month, and the per-parallel-job pipeline
price plus free job/minutes.
- [ ] **Codeberg** — that it remains FOSS-only and free, and its current soft repo/storage caps.
- [ ] **SourceHut** — paid-to-host tiers ($5/$10/$15): the 2026 prices are now *in effect* for new
accounts (confirmed 2026-06-22), so they're no longer "proposed." Note all tiers buy the same
service ("pay what's fair"), with a reduced rate (~the earlier minimum) and financial aid for
hardship — re-confirm before relying on it.
- [ ] **Self-hosted forges** — that Forgejo/Gitea still ship GitHub-Actions-compatible CI, GitLab CE's
current minimum resource footprint, and whether OneDev/Gogs CI status has changed.
- [ ] **"GitHub integrates first" / AI-ecosystem maturity** — re-assess which forges are first-tier
agent and MCP targets; this gap narrows fast.
- [ ] **Self-host/hosted spans** — confirm GitLab still offers CE self-host, and Bitbucket/Azure DevOps
still offer their self-hostable editions, before describing either as spanning both camps.
- [ ] **Credential/token UI** — the "Getting a credential" callout names menu paths and the
write-scope label (`repo` / "read and write") generically; confirm the current wording and
scope name on the default-example host before publishing.
- [ ] Update the comparison's **"as of" date** to the build date.