---
name: hands_on_github_teaching
description: Teach a beginner Git and GitHub on their own real project, do first and explain after. Guided setup, then short lessons covering first commits, reading status and diffs, branches, remotes, pull requests, a deliberate merge conflict, first CI with GitHub Actions, and safe undo. Every lesson ends with a runnable check and a fix path for the errors beginners actually hit, and the course ends with a teach back. Use when someone wants to learn Git or GitHub hands on, asks to be taught version control on a real repo, keeps getting stuck on beginner errors, or needs an onboarding path that goes past setup into pull requests and CI.
---

# Hands-On GitHub Teaching

## Purpose

Most beginner Git material stops at setup and a list of commands. The learner can repeat the commands in the tutorial repo and freezes the first time their own repo does something unexpected. This skill teaches on the learner's real project instead. Each lesson is short, starts with doing a real change to that project, explains only what the learner just saw, and ends with a check that either passes or routes to a specific fix.

The teaching method is deliberate. Doing before explaining keeps the explanation tied to something the learner already touched. The end of lesson check is retrieval practice: the learner has to produce the state or the answer from memory instead of recognizing it on a page. Retrieval practice is one of the best supported study methods in the learning research, which is why every lesson has a check and the course ends with a teach back instead of a summary.

Safety rule that shapes the whole course: the learner works on their own project, so no lesson ever uses a destructive command as a teaching shortcut. No `git reset --hard`, no force push, no deleting branches with unmerged work, until the undo lesson, and there only inside a throwaway practice branch created for that purpose.

## Workflow

### 0. Set up the teaching session

1. Ask what project the learner will learn on. It must be a project they care about but can afford to experiment in. If they have nothing, have them start a tiny real thing, a notes folder, a small script, a personal site. Do not teach on a cloned tutorial repo.
2. Ask their operating system and whether Git is installed. Verify together:
   ```
   git --version
   ```
   No output or command not found means install Git first from git-scm.com or their package manager, then restart the terminal and recheck. Do not proceed past a failed version check.
3. Agree the session rules out loud, in plain words: one lesson at a time, the learner types every command themselves, errors are part of the course and get fixed in the open, and the learner can stop any lesson and ask why.
4. Keep a lesson log as you go: lesson number, date, check result, and any error hit and how it was fixed. The log is what the teach back at the end is built from.

### 1. Lesson structure (use this shape for every lesson)

1. **Do.** Give one small task on the real project, with the exact commands. The learner runs them. Stay quiet about theory.
2. **Explain after.** Explain what just happened in at most five short sentences, tied to the output the learner actually saw. Name the one idea the lesson exists for. Nothing else.
3. **Check.** Run the lesson check. Two kinds are used in this course:
   - State checks, run with the bundled checker: `python3 bin/check_lesson.py <lesson> --repo <path>` prints PASS or the exact missing piece.
   - Recall checks, where the learner answers a question or repeats a task from memory with the notes closed.
   A failed check is not a failed learner. Route to that lesson's fix path, fix it together, and run the check again.
4. **Log it.** One line in the lesson log before moving on.

Never stack two lessons into one sitting for a true beginner. One lesson, one check, one break is the default pace. Two lessons per sitting is the ceiling, and only when both checks passed first try.

### 2. Lesson 0: identity and first configuration

Do:
```
git config --global user.name "Their Name"
git config --global user.email "them@example.com"
git config --global init.defaultBranch main
git config --list
```
Explain after: Git stamps every commit with a name and email, so this is the return address on everything they will ever commit. `init.defaultBranch main` means new repositories start with a branch called main. Git has supported setting that default since version 2.28. `--global` means it applies to every project on this machine, so they do this once, not per project.

Check (recall plus state): learner runs `git config user.name` and `git config user.email` from memory and reads back their own values.

Fix paths:
- Wrong email typo: rerun the same `git config --global user.email` command with the corrected value. It overwrites, nothing else is needed.
- Value set but commits show a different name: a per project setting is overriding the global one. Check with `git config --show-origin user.email` inside the project, which names the file that set the winning value, then set the local value deliberately or remove it with `git config --unset user.email` inside that repo.

### 3. Lesson 1: first repository and first commit

Do, inside the project folder:
```
git init
git status
```
Then the learner picks one small real file, writes or edits it, and:
```
git add <file>
git status
git commit -m "Describe the change in plain words"
git log --oneline
```
Explain after: `git init` turned the folder into a repository, nothing more. The working folder, the staging area, and the commit history are three separate places, and `git status` always tells you which place each change is sitting in. `git add` moves a change into staging, `git commit` moves staged changes into history with a message. Their first commit now exists and `git log --oneline` proves it.

Check: `python3 bin/check_lesson.py 1 --repo <path>` passes when the repo exists, has at least one commit, and the working tree is clean.

Fix paths:
- `fatal: not a git repository`: they are in the wrong folder. `pwd` (or `cd` checking on Windows), move into the project folder, rerun. Never run `git init` in a home directory to make the error go away.
- Commit says it has nothing to commit: nothing was staged. `git status` shows what is unstaged, then `git add` the file and commit again.
- Editor opens and traps them during commit: they ran `git commit` with no `-m`. Teach the exit for their editor, then use `-m` for the rest of the course.
- Committed the wrong file: do not panic and do not reset. If it was the most recent commit and nothing was pushed, `git reset --soft HEAD~1` is introduced here only as a named exception, explained as: uncommit, keep everything staged, fix the staging, commit again. If that feels too advanced for this learner, leave the commit and fix forward with a second commit. Both are acceptable, say so.

### 4. Lesson 2: reading status, diff, and log

Do: learner edits a tracked file in two separate places, stages only one edit if their tooling allows, then runs:
```
git status
git diff
git diff --staged
git log --oneline --graph --all
```
Explain after: `git diff` shows unstaged changes, `git diff --staged` shows what is about to be committed, and comparing the two is how you know exactly what a commit will contain before you make it. Status is the dashboard, diff is the detail, log is the history. Reading these three is 80 percent of daily Git confidence.

Check (recall): close the terminal output. Learner states, for a change they just made, which of the three places it is in and which command would prove it. Then they run that command to verify their own answer.

Fix paths:
- Output scrolls past in a pager and they feel stuck: press `q`. Then set `git config --global core.pager cat` for the rest of the course if the pager keeps biting.
- Diff shows the whole file changed when they touched one line: usually line endings or whitespace from their editor. Show `git diff --ignore-all-space` to confirm, then fix the editor setting rather than committing the noise.

### 5. Lesson 3: branches

Do:
```
git switch -c try-small-change
```
Learner makes one real, small improvement on this branch, commits it, then:
```
git switch main
git log --oneline --graph --all
```
so they can see main does not have the change yet.

Explain after: a branch is a movable label pointing at a commit, not a copy of the project. Switching branches changes which commit their files match. Work on a branch is isolated until it is merged, which is the whole reason branches exist.

Check: `python3 bin/check_lesson.py 3 --repo <path>` passes when at least two branches exist and the current branch is not the only one with commits, or the topic branch exists with its own commit.

Fix paths:
- `git switch` fails with local changes would be overwritten: the learner has uncommitted work that conflicts with switching. Commit it on the current branch first, or set it aside with `git stash push -m "wip"` and `git stash pop` after switching back. Teach stash here only if it actually happens.
- They committed on main by mistake instead of the branch: if nothing was pushed, create the branch at the current commit with `git switch -c try-small-change`, then move main back one commit on the practice understanding from Lesson 1, or simpler for beginners, leave the commit on main and redo the lesson making the next change on the branch. Pick based on the learner, and say why.
- Detached HEAD message appears: they checked out a commit instead of a branch. If they made commits there, save them immediately with `git switch -c rescue-branch`. If they made none, `git switch main` returns to normal. Explain in one sentence: HEAD was pointing at a commit instead of a branch, so new commits had no label to move with them.

### 6. Lesson 4: remotes, GitHub, push and pull

Do:
1. Learner creates an empty repository on GitHub with the same name, no README, no license, no .gitignore (those would create a competing first commit).
2. Connect and push:
   ```
   git remote add origin <url they copied from GitHub>
   git push -u origin main
   ```
3. Learner refreshes the GitHub page and finds their commit there.
4. Learner edits the README or a file directly on GitHub, commits there, then runs `git pull` locally and finds the change on their machine.

Explain after: `origin` is just a nickname for the GitHub address. Push sends commits up, pull fetches commits down and merges them. The `-u` on the first push links local main to origin main so later pushes and pulls need no arguments. GitHub itself holds no special copy, it is one remote among possible remotes, theirs is just the one everyone can reach.

Check: `python3 bin/check_lesson.py 4 --repo <path>` passes when origin exists and local main is not ahead of its upstream.

Authentication, taught here because this is where it bites:
- HTTPS: password login for Git operations is gone. When Git asks for a password over HTTPS, the correct answer is a personal access token, or better, a credential helper such as Git Credential Manager that stores it so they stop being asked.
- SSH: keys avoid tokens, at the cost of setup. The test command is `ssh -T git@github.com`, and success reads as a greeting with their username. All SSH connections to GitHub are made as the user `git`, never as their own username in the URL.

Fix paths, the four errors that end most beginner sessions:
- `Permission denied (publickey)`: the key GitHub has does not match the key being offered. Run `ssh -vT git@github.com`, check the remote is the SSH form with `git remote -v`, confirm the public key is added under their GitHub settings, and confirm they did not generate or use keys under `sudo`, which changes whose keys are in play.
- Push rejected, `non-fast-forward`: the remote has commits they do not have, usually the GitHub side edit from this lesson done out of order, or a second machine. Fix is `git pull` first, resolve anything it raises, then push again. Git refuses the push specifically to stop history being lost, say that out loud.
- `remote origin already exists`: they added it twice. `git remote set-url origin <url>` corrects the address in place.
- Wrong repo pushed or cloned: `git remote -v` shows the truth. Fix the URL with `set-url`. Nothing about the local commits changes.

### 7. Lesson 5: pull requests and review

Do, following the same shape as GitHub's own quickstart:
1. Learner creates a branch, makes a focused change, commits, pushes the branch.
2. On GitHub they open a pull request against main, with a title that says what changed and a short description of why.
3. The teacher (or a second account, or the learner walking away and coming back) leaves one real review comment asking for one small change.
4. Learner makes that change on the same branch, pushes, and watches the pull request update itself.
5. Learner merges the pull request, then locally: `git switch main`, `git pull`, and deletes the merged branch with `git branch -d try-small-change`.

Explain after: a pull request is a proposal to merge one branch into another, with a discussion attached. It is a GitHub feature built on top of Git branches, not a Git command. Small, focused pull requests get reviewed faster and merge cleaner, so one change per pull request is the working habit. Pushing more commits to the same branch updates the open pull request, nothing needs reopening.

Check (state plus recall): the merged change is present on local main after the pull, and the learner can state the five steps (branch, commit, push, open, respond, merge) in order without looking. Missing a step in recall means re-walk it on a second tiny pull request, which is normal and expected.

Fix paths:
- No Compare and pull request button: the branch was never pushed. `git push -u origin <branch>` and refresh.
- Pull request shows far more changes than they made: the branch was created from the wrong starting point or main moved. Look at the Files changed tab together and count. Usually the fix is a fresh branch from current main and cherry picking is too advanced here, so redo the small change on the fresh branch and close the noisy pull request with a comment saying why.
- Merge button blocked by conflicts: that is Lesson 6 arriving early. Do not merge on GitHub. Pull main into the branch locally and go to Lesson 6 now.

### 8. Lesson 6: merge conflicts, on purpose

Do not wait for a real conflict to teach this. Manufacture one in a safe place:
1. Learner creates a practice file, `conflict-practice.txt`, with one line of text, commits it on main, pushes.
2. On a branch, they change that line one way and commit. On main, they change the same line a different way and commit.
3. Back on the branch, `git merge main`. Conflict, guaranteed.

Explain after, while the conflict is on screen: Git merged everything it could and stopped exactly where two changes touched the same lines, because picking a winner there is a human decision. The markers mean: between `<<<<<<< HEAD` and `=======` is the current branch version, between `=======` and `>>>>>>> main` is the incoming version. Resolving means editing the file to the final text a person wants, deleting every marker, then staging and committing to finish the merge.

Do the fix together: edit the file to a deliberate final line, remove all markers, then:
```
git add conflict-practice.txt
git commit
git status
```
Check: `python3 bin/check_lesson.py 6 --repo <path>` passes when no merge is in progress, no conflict markers remain in any tracked file, and the working tree is clean. Then the learner repeats the whole drill a second time alone, with a different line, while the teacher only watches. The second solo run is the real check.

Fix paths:
- Panic option, taught explicitly so they know it exists: `git merge --abort` returns the repo to exactly where it was before the merge started. Using it is allowed. It is a retreat, not a failure.
- Committed with markers still in the file: the commit contains marker text. Fix the file, stage, and commit the correction. On a practice branch nobody else uses, that is the whole fix.
- Conflict during `git pull` instead of merge: same markers, same resolution, then `git commit` to finish. The entry point differs, the skill is identical.
- File they do not know how to resolve: the rule is stop and ask the person who wrote the other side. Never resolve a conflict in code you do not understand by picking a side at random.

### 9. Lesson 7: first CI with GitHub Actions

Do:
1. Learner creates `.github/workflows/ci.yml` on a branch, using the shape that fits their project. Minimal starter for a project with no test suite yet, which still teaches the full mechanism:
   ```yaml
   name: CI
   on: [push, pull_request]
   jobs:
     check:
       runs-on: ubuntu-latest
       steps:
         - uses: actions/checkout@v6
         - run: ls -la
   ```
   For a Node project with tests, swap the last step for the setup and test pattern from GitHub's example workflow: `actions/setup-node@v4` with a pinned node version, `npm ci`, then `npm test`.
2. Learner commits, pushes, opens a pull request, and watches the Actions tab and the check appear on the pull request.
3. Learner deliberately breaks something the workflow catches (a failing test, or for the minimal version, they rename the workflow file to have invalid YAML) on the branch, pushes, and watches CI go red. Then they fix it and watch it go green.

Explain after: a workflow is a YAML file in `.github/workflows` that GitHub runs on machines it provides, called runners, when the events listed under `on` happen. Committing the workflow file and pushing is what triggers it. A red check on a pull request is CI doing its one job: reporting that this branch, tested fresh on a clean machine, does not pass.

Check: the learner's pull request shows a completed check, and the learner can answer from memory: where do workflow files live, what event ran this one, and where do you read the log when a step fails.

Fix paths:
- Workflow never runs: file is not under `.github/workflows`, the extension is not `.yml` or `.yaml`, or a YAML indentation error stopped it before it started. Check the Actions tab for an annotation error, fix indentation with spaces not tabs, push again.
- Workflow runs on push but not on the pull request: `pull_request` is missing from `on`.
- Check is red and the log makes no sense to them: read the log bottom up together. The failing step is the last one that ran, and the error is usually in its final lines. Name that habit explicitly, it is how everyone reads CI logs.
- Action version warnings: pin actions to a major version tag as in the examples, and treat updating them as routine maintenance, not an emergency.

### 10. Lesson 8: safe undo

Teach undo as a ladder, safest rung first. The learner practices each rung on the practice branch, never first on main.

1. Unstage a file: `git restore --staged <file>`. The change stays in the working folder, it just leaves the staging area. Git added `git restore` in version 2.23 for exactly these undo jobs.
2. Discard an uncommitted change to one file: `git restore <file>`. Say the cost plainly before they run it: the uncommitted change is gone, and anything never committed is generally not recoverable.
3. Undo the last commit but keep the work: `git reset --soft HEAD~1`. Commits removed this way leave their changes staged.
4. Undo a commit that is already pushed: `git revert <commit>`. Revert does not erase history, it adds a new commit that reverses the earlier one, which is why it is the safe choice on shared branches. It needs a clean working tree.
5. Recover a commit that seems lost: `git reflog` lists where HEAD has been, and `git branch recover-branch <hash>` puts a label back on a lost commit so it is reachable again. Run this once as a drill: make a commit on a practice branch, reset it away with `--soft`, find it in the reflog, and recover it onto a branch.

Check: the drill commit is recovered and visible with `git log --oneline recover-branch`, and the learner can state which rung they would use for each of: staged the wrong file, committed to the wrong branch, pushed a bad commit to a shared branch.

Fix paths:
- Revert stops with a conflict: the reversal touches lines that changed since. Resolve like Lesson 6, then `git revert --continue`, or back out with `git revert --abort`.
- Reflog does not show the commit they want: it may never have been committed. Check `git stash list` and the editor's local history. If it was never committed or stashed, say plainly that it is gone and rebuild it. Do not invent a rescue that does not exist.
- Temptation to teach `--hard` or force push as a shortcut: refuse it in this course. If a real situation seems to need one, that situation has outgrown the beginner course and gets handled case by case with a backup branch made first: `git branch backup-before-fix`.

### 11. Final: teach back and the solo run

Close the course with two assessments, in this order:

1. **Teach back.** The learner explains, out loud or in writing, with notes closed, how they would take a change from idea to merged pull request with CI green, including what they would do when the push is rejected and when a merge conflict appears. Score against the checklist in `references/teach-back-rubric.md`. Anything they cannot explain is the list of what to reteach, not a grade. Fill the gaps with one more targeted lesson each, then repeat only the missed parts of the teach back.
2. **Solo run.** The learner does one complete real change alone, start to finish: branch, change, commit, push, pull request, respond to one comment, merge, pull main, delete the branch. The teacher observes silently and only intervenes to stop actual data loss. A clean solo run, including recovering from at least one thing going wrong without help, is course complete.

Hand over the learner's own reference at the end: `references/beginner-error-map.md` with their lesson log appended, so the fixes they personally hit stay findable.

## Output Contract

A teaching run produces:

- A lesson log: one entry per lesson with date, check type, pass or the fix path used, and any error hit in the learner's own words where possible.
- A configured machine and a real project under version control, connected to GitHub, with at least one merged pull request and one CI workflow that has been seen both red and green.
- A completed teach back scored against `references/teach-back-rubric.md`, with reteach items listed explicitly, or a clear statement that the teach back has not happened yet.
- A solo run verdict: complete, or the specific step where the learner needed help, which becomes the next lesson.
- No claim of course completion without both the teach back and the solo run. Passing individual lesson checks is progress, not completion.

## Operating Rules

- Teach on the learner's real project. Tutorial repos and toy examples are allowed only for the manufactured conflict in Lesson 6 and the undo drills in Lesson 8, and even those happen inside the real repo on practice branches and files.
- Do first, explain after, every lesson. If an explanation passes five sentences, stop and make the learner do the next thing instead. Theory is delivered in the smallest amount that makes the output on their screen make sense.
- The learner types every command. Watching someone else drive teaches recognition, not recall, and the checks in this course test recall.
- Never run a destructive command on the learner's real work as a shortcut. No `reset --hard`, no force push, no `clean -f`, no branch deletion with `-D`, without a backup branch first and the learner's explicit say so. Default answer to "can I just force it" is no, plus the safe alternative.
- A failed check routes to a fix path, gets fixed, and gets checked again in the same session. Never wave a failed check through to keep the schedule.
- Never invent Git behavior. If a command's behavior is uncertain, check `git <command> --help` or the official docs at git-scm.com and docs.github.com before teaching it. Version differences are real: `git switch` and `git restore` need Git 2.23 or later, the `init.defaultBranch` setting needs 2.28 or later.
- Keep credentials out of the teaching entirely. Tokens and SSH private keys are never pasted into chat, never shown on a shared screen, and never written into the lesson log. If a learner offers one, stop and replace it, then teach the credential helper or key setup instead.
- One error at a time. When three things are broken, fix the first one Git reports, rerun, and let the next error surface. Teaching learners to chase the first error instead of the scariest one is half the course.
- Pace is the learner's. Quality of the checks beats finishing the course in a set number of sessions. A learner who has passed Lessons 0 to 3 solidly is better served than one who was rushed to CI and cannot read `git status`.
