1. 为什么“skills”值得单独开个项目去研究
如果你过去一年经常刷 GitHub,一定注意到很多仓库开始多出一个不太起眼的入口,叫 Skills。名字很朴素,但它解决的是一个非常实际的问题:文档写得再漂亮,读者也不一定能在五分钟内上手;而一个能让人直接在浏览器里动手敲命令、跑代码、过任务的仓库,却能做到“看完就会用”。
我第一次点进 GitHub Skills 的时候,心里想的是“这不就是把教程塞进仓库吗”,直到我完整跟完一个课程才发现,它远不止“教程仓库”这么简单。它把学习路径拆成一个个带验证的小任务,环境是自动化的,进度是自动记录的,甚至连判题都是自动的。这种体验很接近当年在玩“闯关式”教学工具的感觉,但它是长在代码仓库里的,和你的项目、你的分支、你的 PR 流程天然绑在一起。
这个项目适合三类人看:一是开源维护者,想降低新人的贡献门槛;二是技术团队负责人,想给组员做一套内部实操培训;三是像我这样喜欢折腾基础设施的工程师,单纯觉得这套机制很有意思。这篇文章我会把它的核心机制拆开,讲清楚它做了什么、为什么这么做,然后带着你走一遍从零搭建一个 skills 课程的完整流程。
2. 一个 skills 课程背后的技术骨架
别被“Skills”这个名字骗了,它其实是一套由 GitHub 官方定义的项目结构约定,而不是什么新的编程语言或独立平台。官方提供了一个叫“skills”的模板仓库,你基于它去改内容,就能得到自己的课程仓库。用户进入课程后,会根据仓库里的 README 指引逐步完成任务,每一步都可能对应一个 issue、一次 commit、一次 PR 或一个 workflow 的运行结果。
2.1 课程内容:用 Issue 和 Markdown 讲清楚每一步
Skills 课程最核心的内容载体是 GitHub Issues。为什么不用一个独立的页面系统?因为 Issue 本身就是开发者习惯的交流入口,通知、评论、状态都能复用,用户无需离开 GitHub 就能看到“下一步该干嘛”。
所以整个课程的学习路径通常是这样的:课程仓库的 README 里放了一个入口链接,用户点击后会自动创建一个带编号的 Issue,比如“Task 1: 完成你的第一次 commit”。这个 Issue 里用 Markdown 写清楚目标、提示、注意事项,也可能贴出一个模板,用户照着做就行。做完这一步后,用户可以自己 close 这个 Issue,也可以等自动化程序来关闭。
对课程作者来说,写 Issue 远比写一套独立的课程系统要省事。学习者不需要装任何客户端,不需要记住学习平台的地址,所有信息都在他原本就在使用的 PR、Issue 和通知流里。我自己第一次看课程的时候,最大的感受就是“轻”——它不是让你去另一个网站学完再回来写代码,而是真的就在你每天开 PR 的同一个地方完成全部动作。
2.2 练习环境:devcontainer 把环境“固化”进仓库
课程的实操环节不可能只靠嘴巴讲,再简单的任务也需要一个跑命令的地方。Skills 的做法是用 devcontainer 把整个实验环境写进仓库里,配合 GitHub Codespaces 一键启动。
每个仓库里都可以有一个.devcontainer/devcontainer.json文件,它描述了这个项目需要的开发环境:基础镜像、装哪些工具、要不要预装语言运行时、启动时执行什么命令。用户点一下“Open in Codespaces”,就会得到一个和作者完全一致的终端环境,而不是“在我的机器上可以跑”的那种薛定谔环境。
这个设计背后的思路很值得借鉴。传统教程最容易被吐槽的一句话就是“我按着你的步骤来了,但报错了”,绝大多数报错都出在环境差异上。而 devcontainer 把环境差异消灭在了源码层面,相当于把“环境”本身当成一个文件来管理。对一个教人写代码的课程来说,环境变量、版本号、依赖锁定这三样做到一致,成功率就已经提高了八成。
2.3 自动校验:用 GitHub Actions 当“阅卷老师”
如果课程只是贴出步骤让用户自测,那和普通 README 没太大区别。Skills 的灵魂在于每一步都有自动校验。GitHub Actions 会被触发器调用,在后台检查用户的执行结果,比如是否创建了某个文件、是否在指定分支提交了指定内容、是否完成了某次 PR。
自动校验其实讲的是“反馈闭环”。没有闭环的学习内容,用户做完了也不知道对不对,出了问题也难定位;有了 Actions 的自动检查,用户改完代码、提交完 PR,几分钟后就会收到通过或失败的通知,甚至能看到失败日志里的详细提示。
从搭建者的视角看,Actions 的工作流文件就是“阅卷规则”。你可以用现成的skills-action官方 action,也可以直接写普通 shell 脚本做检查。这意味着课程校验逻辑并没有被关在一个黑盒里,它是完全透明的,发生在仓库的.github/workflows目录下。这可以说是整个设计里最开放、也最灵活的一点。
3. 实操:手把手搭一个最小可运行的 skills 课程
理论讲完,还是得上手。这一节我会带着你从零搭一个能用的 skills 课程,目标很简单:教会用户完成一次“常规的 Pull Request 提交”。如果你跟着做一遍,就会发现这套东西的骨架极其简单,真正花时间的反而是如何设计好的任务。
3.1 定义课程目标与步骤拆解
任何课程都从目标开始。我给这个示例定的目标是:用户能从一个新的 clone 仓库出发,创建自己的分支,改一个文件,提交 commit,并成功提交一个 Pull Request。听起来简单,但对刚上手 GitHub 的人来说,这一步就是最实用的技能之一。
然后我把整个过程拆成两个任务:
- 第一步:创建你的第一个 feature 分支
- 第二步:交出你的第一个 Pull Request
为什么不拆成三步?因为主题越聚焦,每一步的自动校验就越简单,用户也越不容易中途放弃。Skill 课程的节奏应该是“一周目轻松过关”,而不是“一关卡半小时”。很多人第一次设计课程时容易把任务拆得过细,反而让学习者觉得自己在被“手把手地教做事”。我的经验是:一个五分钟能完成、且结果能被自动验证的任务,就是合适的任务粒度。我建议你按这个标准去重新审视你规划的每个步骤。
3.2 配置 devcontainer 环境的三个关键文件
环境配置这一环,我建议直接用官方模板仓库起步,省掉很多初始化工作。核心目录结构大概是这样的:
先看devcontainer.json,这是 Codespaces 读取的入口文件:
{ "name": "skills-example-env", "image": "mcr.microsoft.com/devcontainers/universal:2", "postCreateCommand": "git --version", "customizations": { "vscode": { "settings": {}, "extensions": [] } } }image决定整个环境的底座,我选了 GitHub 官方维护的 universal 镜像,里面已经装好了 git、node、python 等常用工具,对新手课程来说非常省心。postCreateCommand是环境启动后自动执行的命令,这里我只是验证一下 git 版本,确保基础环境正常。
第二步是仓库里要有一个足够简单的 README,里面放入口链接。这个链接的格式是:
https://github.com/你的用户名/仓库名/issues/new?template=task-1.md这个链接会引导用户创建一个基于task-1.md模板的新 Issue。为了让它正常工作,你要在.github/ISSUE_TEMPLATE/目录下准备好两个模板文件,比如task-1.md和task-2.md。模板里写清楚任务内容,以及用户完成后的操作指引。
最后是 Actions 工作流文件。以第一关“创建分支”为例,校验逻辑就是检查当前仓库是否出现一个特定名字的分支。文件放在.github/workflows/下:
name: validate-task-1 on: push: branches: - 'user-*' jobs: check-branch-exists: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: | branch_name="${GITHUB_REF#refs/heads/}" if [[ "$branch_name" != user-* ]]; then echo "分支名不符合要求" exit 1 fi这段逻辑并不复杂:只要有人把分支推上来,就触发校验,脚本看一下分支名格式是否以user-开头。它是整个课程“自动阅卷”的雏形,也是你后续扩展所有关卡的基础模板。
3.3 用 Actions 工作流实现自动批改
校验分支是否存在只是入门级,Skills 里更有价值的批改场景是“检查一次 PR 是否被正确提交”。我先给它设计一个更实用的规则:用户提交的 PR 必须修改了README.md且在文件中新增一行自己的名字。
这个校验需要监听pull_request事件,并在工作流里读取变更内容。一个精简写法是这样:
name: validate-task-2 on: pull_request: types: [opened, reopened, synchronize] jobs: check-pr-content: runs-on: ubuntu-latest permissions: pull-requests: write issues: write steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - name: 检查变更文件 run: | if ! git diff --name-only HEAD~1 HEAD | grep -q "README.md"; then echo "PR 没有修改 README.md" exit 1 fi - name: 检查新增内容 run: | if ! grep -q "learner" README.md; then echo "README.md 中未找到 learner 标记" exit 1 fi comment-result: needs: check-pr-content if: always() runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: 评论 PR uses: actions/github-script@v7 with: script: | const success = '${{ needs.check-pr-content.result }}' === 'success'; github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: success ? '恭喜,任务通过!' : '很遗憾,任务未通过,请查看日志。' });这里有两个细节要解释。第一个是pull_request的synchronize事件,它保证用户每次更新 PR 后校验都会重新跑。第二个是 guthub-script action 的自动评论,它让用户不离开 PR 页面就能知道结果。这套模式既是“判题”,也是“反馈”。
实战里还要注意一个问题:如果你只用git diff HEAD~1 HEAD,当用户一次提交了多个 commit 时,比较对象就不太准。稳妥做法是取pull_request.base.sha与head.sha做比较,或者直接用actions/github-script拉取 PR 的 files 列表。我在自己的课程里是直接调用 GitHub API 拿变更文件列表的,比 diff 当前 commit 更可靠。
4. 我在复跑课程时踩过的坑与排查实录
搭建完课程模板后,我自己开了一个全新的 Codespaces 环境,把整个课程从头到尾跑了一遍。过程远没有想象中顺利,中间踩了好几个坑,每一个都很典型,拿出来分享比我空口讲概念有用得多。
4.1 常见问题速查表
我把最常碰到的几类问题整理成一张表,方便你对照排查。
| 问题现象 | 可能原因 | 排查思路 |
|---|---|---|
| 入口链接打开后提示模板不存在 | .github/ISSUE_TEMPLATE/目录下缺少同名模板文件 | 确认目录下的文件名和链接中的.md名字完全一致 |
| Cloud Codespaces 反复启动失败 | devcontainer.json里的镜像名拼错或网络拉取失败 | 先在本地跑docker pull验证镜像是否存在 |
| Actions 工作流没有触发 | 事件监听写错,或仓库默认分支不是main | 确认on.push.branches的配置与默认分支一致 |
| 自动评论不生效 | 工作流权限未开启pull-requests: write | 在 workflow 的permissions里显式声明写权限 |
| 用户完成后课程进度不显示 | 组织级的 Skills 配置与仓库级配置冲突 | 去仓库的 Insights 页面查看 Active users,确认课程是否已加入组织列表 |
这里特别提醒一下权限问题。如果你用的是个人仓库,默认的 GITHUB_TOKEN 权限比较受限,自动评论常常会静默失败——不是报错,而是不产生任何效果。我当时查了快两个小时,最后发现在 workflow 顶部补上permissions: pull-requests: write就解决了。这个经验对所有用 Actions 做交互的仓库都适用。
4.2 几条独家避坑经验
第一,模板 Issue 里一定要写清楚“完成标志”。大多数学习者卡住的点不是不会做,而是不确定“做到什么程度就算完成”。我后来在每个模板末尾加了一个检查清单,让用户自己在提交 PR 前确认勾选,完成率立刻有明显提升。
第二,校验逻辑尽量保持单一职责。比如检查 PR 修改的是否是README.md就把这个检查单独放在一个步骤,检查内容是否包含关键词就放另一个步骤。拆开的好处是失败时日志能把原因说得非常清楚,用户一眼看出是“改错了文件”还是“内容不对”,而不是看到一个笼统的“失败”。
第三,如果你用的是组织仓库,建议把课程加进 GitHub 官方的 Skills 列表中,这样用户能直接在 Explore 页面搜索到你的课程。这一步不需要额外写代码,但要走组织的设置页面,值得在做完课程后认真操作一遍。
第四,调整postCreateCommand时务必测试二遍。我在某个课程里加入了一条自动执行npm install的命令,结果因为安装耗时长,Codespaces 环境一直显示“初始化中”,好几个用户跑来问是不是环境坏了。后来我把它改成手动引导安装,并在 README 里明确写了一句“启动后请先运行 npm install”,反而更顺畅。
5. 这事的后续扩展空间
走通整套流程之后,我开始琢磨这玩意儿能变出什么花样,而不是仅仅当一个“课程模板”。
最直接的应用场景是新成员入职培训。以前招来一个人,给他几份文档,大家自己看,看完也不知道会不会。现在可以做一个仓库,里面放几个 skill 任务,从 clone 仓库、改代码、提 PR 到跑 CI 全都能自动校验。新人是否完成了任务,一查进度就知道,省去管理员大量重复答疑。
团队内部想做技术分享也可以用这个形式。之前我做过一次“如何写好 Dockerfile”的分享,讲完就完了,实际能动手的机会很少。如果把它做成 skills 仓库,每人都能开一个 Codespaces 环境,在真实的容器构建过程里完成几个小任务,动手体验和听 PPT 完全不同。
还有一个思路是把 skills 和内部发布流程结合起来。比如新版本上线前,让核心用户做一个“验证任务”,在临时环境里跑通关键用例。这个任务完成后提交一个 PR,上面自动带上验证记录,相当于把人工测试流程留下了一条可追溯的痕迹。我试过这个玩法,运维同学觉得比在群里汇报“我测过了”要靠谱得多。
关于动手实践方面,建议你先别急着做一套完整课程,拿一小时搭一个最小闭环就好:一个 README 入口、一个 Issue 模板、一个 Actions 校验、一个 Codespaces 环境。把最小闭环跑通了,再往里填内容,这样会节省很多调试时间。我自己第一版课程内容写了很多,结果环境问题排查了两天,后来推倒重来,先把一个任务跑通,再把其他任务逐个加进去,效率反而高出不少。