Skip to content

Digital-Educational-Engineering/github-course-repo-assistant

Repository files navigation

github-course-repo-assistant

Small standalone scripts for common GitHub course repository administration tasks.

This repository is shared in response to uncertainty around GitHub Classroom workflows and availability. See the related GitHub Community discussion: https://github.com/orgs/community/discussions/196615

The goal is not to replace GitHub Classroom or become a large framework. The goal is to provide a small, understandable toolkit that instructors can copy, configure, dry-run, inspect, and run safely.

Table of Contents

What This Is For

Use this toolkit when you want to:

  • Create one private repository per student from a template repository.
  • Create one private repository per team from a template repository.
  • Invite students to those repositories.
  • Check current repository permissions without changing anything.
  • Make student access read-only after a deadline.
  • Make student access writable again when needed.

This toolkit intentionally uses a few standalone scripts instead of one large application. Each script has its own matching YAML config file so instructors can see exactly which settings affect the task they are running.

The safe workflow is always the same:

  1. Edit the matching .yml file.
  2. Keep dryRun: true.
  3. Run the matching .js script.
  4. Check the terminal output and CSV report.
  5. Change dryRun to false only when the dry run looks right.
  6. Run again and type yes to confirm real GitHub changes.

Files

create-repos.js                 Creates repos from a template and invites students
create-repos.yml                Settings for create-repos.js
update-permissions.js           Reports or changes student permissions
update-permissions.yml          Settings for update-permissions.js
students-course101.example.csv  Example course roster
.env.example                    Example GitHub token file
package.json                    Node.js dependencies and commands
LICENSE                         MIT License

Each script has its own YAML file with the same name. This keeps configuration local to the task an instructor is running.

If you manage multiple courses, assignments, or semesters, it'll be easier to create copies of the YAML and CSV files with descriptive names, such as create-repos-course101-assignment1.yml and students-course101-fall2026.csv, so each course or assignment can keep its own roster, repository prefix, and dry-run settings. This way you don't need to edit YAML files back and forth or worry about accidentally running a script with the wrong settings. See the Managing Multiple Courses section below for more on that.

Install

You need Node.js 18 or newer and a GitHub token that can create repositories and manage collaborators in your GitHub organization.

From this folder, install dependencies:

npm install

Create your local token file:

cp .env.example .env

Edit .env:

GITHUB_TOKEN=your_token_here

Do not upload .env to GitHub. It is ignored by .gitignore.

GitHub Token Setup

The scripts use GITHUB_TOKEN from your local .env file. The token should belong to an instructor or service account that is allowed to administer the course repositories.

GitHub's token documentation is here: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens

The token needs enough access to:

  • Read organization repositories and repository teams.
  • Create private repositories in the organization from a template repository.
  • Invite collaborators to repositories.
  • Update repository collaborator, invitation, and team permissions.

For a classic personal access token, instructors commonly need scopes such as repo and admin:org, depending on organization settings.

Treat the token like a password. Keep it only in .env, and rotate it if it is exposed.

Course Roster CSV

Start by copying the example roster:

cp students-course101.example.csv students-course101.csv

The example columns are:

StudentName,StudentID,GitHubUsername,TeamName
Example Student One,100000001,example-student-one,1
Example Student Two,100000002,example-student-two,1
Example Student Three,100000003,example-student-three,2

Both YAML files can point to the same roster file:

studentCsv: "students-course101.csv"

create-repos.js uses the roster to decide which repositories to create and which students to invite.

update-permissions.js reports current repository access or changes student access to read-only or writable for matching repositories. By default, it uses the roster to avoid changing direct collaborators or pending invitations for people who are not listed as students, which helps protect instructor and TA accounts.

Individual Repositories

For one repository per student, use these roster columns:

  • StudentName
  • StudentID
  • GitHubUsername

Your roster may still include TeamName if the same CSV is also used for team assignments in the same course. In individual mode, create-repos.js ignores TeamName and does not allow {TeamName} in repoNamePattern.

Team Repositories

For one repository per team, use these roster columns:

  • StudentName
  • StudentID
  • GitHubUsername
  • TeamName

Students with the same TeamName value are invited to the same repository.

Create Repositories

Edit create-repos.yml first:

org: "YOUR-GITHUB-ORGANIZATION"
templateRepo: "assignment-template" # The template repository must already exist in the organization.
studentCsv: "students-course101.csv"
dryRun: true # Set to false only when you are ready to create repositories and invite students.

The template repository must already exist and be marked as a template repository on GitHub (go to the repository Settings and check "Template repository"). The script creates new repositories from the template, so any starter code, README content, or other files in the template are copied into each student repository when it is created. If there are multiple commits in the template, the new repositories will squash that history into one commit.

This toolkit creates repositories from a template instead of forking starter code. That means later starter-code changes cannot be synced into already-created student repositories with GitHub's fork sync tools. This is an intentional tradeoff: fork-based classroom workflows can create serious academic misconduct risks when student repositories remain connected through fork relationships. See the related Global Campus Teachers discussion: https://github.com/community/Global-Campus-Teachers/discussions/368

Run a dry run. Always pass the YAML config file explicitly:

node create-repos.js create-repos.yml

When the output and report look right, change dryRun to false and run again:

node create-repos.js create-repos.yml

Type yes only when you are ready to create repositories or invite students.

After sending invitations, tell students to accept them as soon as possible. GitHub repository invitations expire after seven days. If some students miss the window, rerun create-repos.js with the same config. The script checks accepted collaborators and active pending invitations first, so it will not create duplicate access for students who already accepted or already have a matching pending invitation. If an invitation expired or is otherwise missing, the script sends the invite again.

At the end of the run, create-repos.js checks for active invitations that still have not been accepted. It prints a short console summary and includes unaccepted-invitation rows in the CSV report so instructors have a file they can use for follow-up.

Individual Assignment Repos

Use this when each student gets a separate repository:

mode: "individual"
repoNamePattern: "assignment-1-{GitHubUsername}"

For GitHub username example-student-one, this creates:

assignment-1-example-student-one

Individual repository creation uses StudentName, StudentID, and GitHubUsername. It ignores TeamName, even when the roster includes that column for other assignments.

Available individual repository-name placeholders are {GitHubUsername}, {StudentName}, and {StudentID}.

For individual repositories, prefer a pattern with one unique identifier, such as {GitHubUsername} or {StudentID}. Student names are not guaranteed to be unique, so avoid using {StudentName} as the only student-specific part of the repository name. It is also usually easier to manage repository names later if you use one identifier rather than combining several identifiers with multiple separators.

Placeholder values are cleaned before they become part of a repository name: leading and trailing spaces are removed, spaces in the middle become hyphens, and characters that are not safe in GitHub repository names are replaced. For example, Mary Ann Smith becomes Mary-Ann-Smith.

The default pattern uses the student's GitHub username:

repoNamePattern: "assignment-1-{GitHubUsername}"

If your course tracks students primarily by institutional ID, this is also a good simple pattern:

repoNamePattern: "assignment-1-{StudentID}"

Team Assignment Repos

Use this when students work in teams:

mode: "team"
teamRepoNamePattern: "assignment-1-team-{TeamName}"

With the default settings, numeric-only TeamName values are padded to two digits. Team 1 becomes 01, so the repository is:

assignment-1-team-01

The padding amount is configurable in create-repos.yml:

padTeamNumbers: true
teamNumberPadding: 2

Set teamNumberPadding: 0 if you do not want numeric team names padded. With that setting, team 1 stays 1.

Team repositories use the standard roster columns, including TeamName to group students into repositories.

Available repository-name placeholders are {GitHubUsername}, {StudentName}, {StudentID}, and {TeamName}.

The same repository-name cleanup applies to non-numeric team names. For example, Project Team 1 becomes Project-Team-1. It is not padded because the full value is not just a number.

Update Permissions

Use update-permissions.js after a deadline, or whenever you need to check or change student access for matching repositories.

Edit update-permissions.yml:

org: "YOUR-GITHUB-ORGANIZATION"
repoPrefix: "assignment-1-"
studentCsv: "students-course101.csv"
permission: "print-only"
dryRun: true

The script finds non-template repositories whose names start with repoPrefix.

Use a specific prefix, such as assignment-1-, so the script only reports or changes the repositories for the intended assignment.

By default, direct collaborators and pending invitations are changed only if their GitHub username appears in studentCsv:

onlyUsersFromCsv: true

If instructor or TA accounts may have non-admin access, add them to skipUsers:

skipUsers:
  - "ta-github-username"

If an organization team should never be changed, add either its name or slug to skipTeams:

skipTeams:
  - "teaching-staff"

Admin users and admin teams are always skipped.

Print Current Permissions

Use this first to see what the script finds:

permission: "print-only"
dryRun: true

Run:

node update-permissions.js update-permissions.yml

No permissions are changed in print-only mode.

Make Repos Read-Only

Use this after a deadline:

permission: "read-only"
dryRun: true

Run the dry run first, check the report, then set:

dryRun: false

Run again and type yes to confirm.

Make Repos Writable Again

Use this when students need push access again:

permission: "write"
dryRun: true

Run the dry run first, check the report, then set dryRun: false and run again.

Managing Multiple Courses

For multiple courses, assignments, or semesters, copy the YAML and CSV files and keep the names descriptive:

students-course101-fall2026.csv
create-repos-course101-assignment1.yml
update-permissions-course101-assignment1.yml

Always pass the config file name when running a script:

node create-repos.js create-repos-course101-assignment1.yml
node update-permissions.js update-permissions-course101-assignment1.yml

This lets each course or assignment keep its own roster, repository prefix, and dry-run settings.

Reports

Both scripts write CSV reports into the reports/ folder.

Reports show what was found, what would change during a dry run, what changed during a real run, and any GitHub API errors.

The reports/ folder is ignored by Git by default.

GitHub API Rate Limits

GitHub limits how many API requests a token can make in a period of time. For larger courses, these scripts may need to make many requests while checking repositories, inviting students, listing permissions, or updating access.

The scripts watch GitHub's primary rate-limit response headers. If GitHub reports that the limit is exhausted, the script prints a message and sleeps until GitHub's reported reset time, with a small buffer, before continuing. This can look like the script is paused for several minutes, but it is waiting so the run can continue safely instead of failing partway through.

GitHub's REST API rate-limit documentation is here: https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api

Troubleshooting

GITHUB_TOKEN is missing

Create a .env file and add:

GITHUB_TOKEN=your_token_here

Could not find config file

Make sure you are running the command from this folder, and that the .yml file exists.

Both scripts require the config file path. For example:

node create-repos.js create-repos.yml
node update-permissions.js update-permissions.yml

Could not find student CSV file

Check the studentCsv value in the YAML file you are using.

CSV file is missing the configured GitHub username column

Your roster column names do not match the csvColumns section in the YAML file. Either rename the CSV columns or update the config.

GitHub says Not Found

For create-repos.js, the script checks the organization first, then the template repository, and prints which GitHub resource it was trying to access.

Common causes:

  • The organization name is wrong.
  • The token account cannot access the organization.
  • The template repository name is wrong.
  • The template repository exists but is not visible to the token account.
  • The template repository is not marked as a template repository in GitHub settings.
  • The token does not have access to the organization or repository.
  • The token belongs to an account that cannot administer the target repositories.

GitHub says Bad credentials

The token is missing, expired, copied incorrectly, or does not have the needed access.

About

Small instructor-friendly scripts for creating course GitHub repositories, inviting students, and safely changing repository permissions using simple YAML config files.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors