How to keep auto-grading programming assignments now that GitHub Classroom has closed

GitHub Classroom was deprecated as of 28 August 2026, so its autograding is gone. To keep auto-grading, send each student's program to OnlineCompiler.io with the same inputs and expected outputs: one request to /api/run-tests/ returns a verdict for every test and a score, and a stored problem keeps the tests, hidden ones included, on our side. Students submit from a page you control or from your own course site, and what does not carry over is the repository workflow, which is covered below.

By the OnlineCompiler.io team. Updated .

What exactly closed, and when?

GitHub's changelog of 27 August 2026 says GitHub Classroom is deprecated as of 28 August 2026, completing a transition it first announced on 26 May 2026. The Classroom website, its APIs and related services have been decommissioned. Some data is permanently deleted under GitHub's retention policies, such as classroom and assignment names, tests defined outside repositories and LTI rosters. GitHub user accounts, repositories and organizations created for Classroom are not affected, so the assignment repositories and the student work in them are still yours to read.

GitHub's migration page recommends two partner platforms, Codio and Classroom 50, and explains how to export your Classroom data as JSON or CSV files with the GitHub CLI. Export first: that page is where the class lists and grades you want to keep come from.

What were the autograding tests, and which part moves over?

The commonest Classroom test was an input/output test: run a command, give it some text on standard input, and compare what it prints with the expected text. GitHub's input/output action takes a test name, a command, the input, the expected output, a comparison method, a timeout and a maximum score. A step for a program that prints its input in capital letters looks like this:

A Classroom input/output test (from the action's README format)
- name: Shout
  uses: classroom-resources/autograding-io-grader@v1
  with:
    test-name: 'shout hello'
    command: 'python3 shout.py'
    input: 'hello'
    expected-output: 'HELLO'
    comparison-method: 'exact'
    max-score: 2

Each part has a counterpart in a run-tests request:

Classroom testRun tests
commandcompiler and code: you send the program's source, not a command to run
inputtests[].input
expected-outputtests[].expected_output
comparison-method: exactchecker: "exact", or "trim" (the default) to forgive trailing spaces and blank lines
comparison-method: included or regexNo matching checker. Write the expected output in full, use "tokens" to ignore spacing, or "float" for numbers within a tolerance
timeout (minutes in the action, 10 by default)time_limit in seconds (2 by default, up to 30)
max-scoretests[].weight, with "scoring": "weighted"
setup-commandNone: runs have the language's standard library and no package installs

That is the whole translation. A test that was a build or a framework run (a Makefile, pytest, mvn test) has no counterpart, because each test here runs one program against one input; see what does not carry over.

How do I turn those tests into one request?

The three tests of the example (weights 2, 3 and 5, the last one hidden) become the tests list. To run it:

  1. Get a free API key (100,000 runs a month, no credit card).
  2. Put the student's source in code and its language in compiler (one of 46 languages).
  3. List the tests, each with its input, expected_output and weight. Mark a test "hidden": true to keep its output out of the response.
  4. Set "checker": "exact" and "scoring": "weighted", then send it to https://api.onlinecompiler.io/api/run-tests/.
Terminal
curl -X POST https://api.onlinecompiler.io/api/run-tests/ \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "compiler": "python-3.14",
    "code": "print(input().upper())",
    "tests": [
      {
        "input": "hello\n",
        "expected_output": "HELLO\n",
        "weight": 2
      },
      {
        "input": "Hello, World\n",
        "expected_output": "HELLO, WORLD\n",
        "weight": 3
      },
      {
        "input": "ok\n",
        "expected_output": "OK\n",
        "weight": 5,
        "hidden": true
      }
    ],
    "checker": "exact",
    "scoring": "weighted"
  }'

For print(input().upper()) every test passes and the score is 100. The response, with the signed receipt shortened:

Response (200)
{
  "verdict": "accepted",
  "score": 100.0,
  "passed": 3,
  "total": 3,
  "tests": [
    {"verdict": "accepted", "hidden": false, "time": "0.0689", "memory": "13724", "exit_code": 0,
     "output": "HELLO\n", "error": "", "truncated": false, "files": [], "files_truncated": false},
    {"verdict": "accepted", "hidden": false, "time": "0.0724", "memory": "13956", "exit_code": 0,
     "output": "HELLO, WORLD\n", "error": "", "truncated": false, "files": [], "files_truncated": false},
    {"verdict": "accepted", "hidden": true}
  ],
  "summary": "2 of 2 sample tests passed. 1 of 1 hidden test passed.",
  "submission": "sub_J1ocGQ0No3CVoeFII6MEIfP9",
  "receipt": {"receipt": "rcpt_dRe7WfsjzZIHjNpxiPFuKvbc", "...": "..."}
}

For a student who wrote print(input().title()), the output is wrong on every test. The visible tests show what the program printed, the hidden one shows only its verdict, and the score is 0:

Response (200)
{
  "verdict": "wrong_answer",
  "score": 0.0,
  "passed": 0,
  "total": 3,
  "tests": [
    {"verdict": "wrong_answer", "hidden": false, "output": "Hello\n", "...": "..."},
    {"verdict": "wrong_answer", "hidden": false, "output": "Hello, World\n", "...": "..."},
    {"verdict": "wrong_answer", "hidden": true}
  ],
  "summary": "0 of 2 sample tests passed. Sample 1 expected HELLO but the program printed Hello. 0 of 1 hidden test passed.",
  "...": "..."
}

The score follows the weights: with weighted scoring it is 100 times the weights of the accepted tests over all the weights, so a student who passes only the 2-point test gets 20. The run-tests reference lists every verdict and checker. Each test counts as one run of your monthly allowance.

Can I try the grading before I sign up?

The editor below runs the example's program against the same three tests, without a key. Change the code, for instance to print(input().title()), and press Run tests.

print(input().upper())

Test cases

Up to 8 tests. Each runs the code above with its own input.

  1. Test 1

  2. Test 2

  3. Test 3

It compares ignoring trailing spaces by default; the checker menu offers the others. For more on this tool, see online compiler with test cases.

How do I keep the tests, and the hidden ones, on the server?

With run-tests your script sends the tests with every submission, so the script holds them. A stored problem keeps them for you: you create it once, publish it, and from then on a student's code goes to the problem by its slug or id and nothing else travels with it. Hidden tests are readable only with your API key.

The same three tests as a problem, with a statement, starter code and a reference solution:

shout.json
{
  "title": "Shout",
  "slug": "shout",
  "statement": "Read one line and print it in capital letters.",
  "languages": ["python-3.14"],
  "checker": "exact",
  "scoring": "weighted",
  "tests": [
    {"name": "hello", "input": "hello\n", "expected_output": "HELLO\n", "hidden": false, "weight": 2},
    {"name": "sentence", "input": "Hello, World\n", "expected_output": "HELLO, WORLD\n", "hidden": true, "weight": 3},
    {"name": "already loud", "input": "ok\n", "expected_output": "OK\n", "hidden": true, "weight": 5}
  ],
  "starter_code": {"python-3.14": "line = input()\n# print it in capital letters\n"},
  "reference_solution": {"compiler": "python-3.14", "code": "print(input().upper())\n"}
}

Create it, publish it and submit to it:

Terminal
# 1. Create the problem from shout.json (above). The answer is 201 with the problem's id.
curl -X POST https://api.onlinecompiler.io/api/problems/ \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d @shout.json

# 2. Publish it. The reference solution is judged against the tests first, so a wrong expected output is caught here.
curl -X POST https://api.onlinecompiler.io/api/problems/shout/publish/ \
  -H "Authorization: YOUR_API_KEY"

# 3. A student's code goes to the problem by its slug or id and gets the verdicts back.
curl -X POST https://api.onlinecompiler.io/api/problems/shout/submissions/ \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"compiler": "python-3.14", "code": "print(input().title())", "external_ref": "student-0417"}'

Publishing first judges the reference solution against the tests, so a mistake in an expected output is caught before any student meets it. The publish answer says it is checking:

Response to step 2 (202)
{"problem": "prob_f67dqajmOBD6BqR4kKLHeNe4", "version": 1, "state": "checking", "check": "sub_dKiGZje8dquj0Z86aOPPnY4u"}

When the check passes, the problem's GET /api/problems/shout/ shows the version as published:

Response (200), shortened
{
  "problem": "prob_f67dqajmOBD6BqR4kKLHeNe4",
  "slug": "shout",
  "version": 1,
  "check": {"version": 1, "state": "published"},
  "...": "..."
}

The submission in step 3 is the same wrong program as above. The answer is the run-tests response plus the problem and version; the two hidden tests show only their verdict, and the external_ref you sent (use an opaque id of your own, not a student's name or email) comes back in the receipt:

Response (200), shortened
{
  "verdict": "wrong_answer",
  "score": 0.0,
  "passed": 0,
  "total": 3,
  "tests": [
    {"verdict": "wrong_answer", "hidden": false, "output": "Hello\n", "...": "..."},
    {"verdict": "wrong_answer", "hidden": true},
    {"verdict": "wrong_answer", "hidden": true}
  ],
  "problem": "prob_f67dqajmOBD6BqR4kKLHeNe4",
  "version": 1,
  "summary": "0 of 1 sample test passed. Sample 1 expected HELLO but the program printed Hello. 0 of 2 hidden tests passed.",
  "submission": "sub_ObNfaOoJv9Zg7B1X48aa2EZA",
  "receipt": {"receipt": "rcpt_Txi0EHatJVHepOwseFyXromU", "external_ref": "student-bad", "...": "..."}
}

Edit the tests later and publish again: a new version is made, and submissions can be re-judged against it. A problem can also have data files, drivers and a custom checker; the stored problems docs cover them.

How do students submit their code?

There are two ways, and both keep your API key off the student's computer:

  1. Your own page or LMS calls the API. The student uploads or pastes code into a course site, a form or an LMS plugin you control; your server sends it to the problem (or to run-tests) and writes the score to your gradebook. We do not connect to Google Classroom, Canvas or any other LMS for you: your code makes the call, and the LMS needs a way for it to run. The one LMS route we document is Moodle CodeRunner.
  2. The widget in problem mode. Put the editor on a page you control. The student sees the statement, the starter code and the visible tests, can press Run samples to try the visible tests, and can Submit up to the number of times you allow. It needs one server-side step: your server creates a session for that student.

A session lets one student's page submit to one problem without holding your key. It lasts up to 24 hours and takes 1 to 20 Submits.

On your server (pip install requests)
import requests

# On your server, with your REST API key. Never put the key in the student's page.
reply = requests.post(
    "https://api.onlinecompiler.io/api/problems/prob_f67dqajmOBD6BqR4kKLHeNe4/sessions/",
    headers={"Authorization": "YOUR_API_KEY"},
    json={"external_ref": "student-1042", "max_submits": 3, "expires_in": 7200},
    timeout=30,
)
reply.raise_for_status()
session = reply.json()
print(session["token"])  # render this into the student's page; it is shown once
The session (201), shortened
{
  "session": "pss_J0LuYx1xnNzWyx9SAtx3lURs",
  "problem": "prob_f67dqajmOBD6BqR4kKLHeNe4",
  "version": 1,
  "external_ref": "student-1042",
  "max_submits": 3,
  "submits_used": 0,
  "expires": "2026-11-06T16:11:44Z",
  "token": "pst_EfDb3QRAQGanqzG1tPR7JWCLIvMUMP2c",
  "...": "..."
}

Render the token into the page, with a widget key from your dashboard:

assignment.html
<div class="runcode" data-key="YOUR_WIDGET_KEY"
     data-problem="prob_f67dqajmOBD6BqR4kKLHeNe4"
     data-session="pst_...the token your server just received..."></div>
<script src="https://onlinecompiler.io/widget.js"></script>

Record scores on your server, never from the page. A student's browser can show a result, but take the score from the signed webhook or from GET /api/submissions/{id}/, since anything the page reports can be changed. The widget's problem mode section has the events and the webhook details, and the embeddable editor page covers the widget itself.

What does not carry over from GitHub Classroom?

  • Repositories and pull requests. Students do not push to a repository here, and nothing comments on their commits. Your assignment repositories on GitHub stay as GitHub left them.
  • GitHub-based feedback. The Actions log and the checks on a pull request are gone with the workflow. Feedback here is the response or the widget: a verdict per test, the visible tests' output and a summary line.
  • Rosters, deadlines and the gradebook. Your course site or LMS keeps the class list, the due dates and the grades. We return a score; where it goes is up to you.
  • Several-file and framework tests. One request runs one program. It can send extra files that the program reads, as the C++ header files guide shows, but there is no make, pytest or mvn step.
  • Substring and regex comparison. There is no included or regex checker (see the table above).
  • Plagiarism checks. None.

If the repository workflow is why you used GitHub Classroom, pick a platform that keeps it (next section). If your assignments are programs that read input and print output, those are the tests that move over unchanged.

What do other instructors use instead?

This is how the main options compare, each as its own source describes it:

OptionWhat it isWhat it does well
Classroom 50Free and open source, from the Fifty Foundation. It keeps the GitHub workflow: assignment repositories and grading by GitHub Actions in your own organization, which needs a Team or Enterprise plan (its wiki says it is free for verified teachers).The closest to Classroom, and recommended by GitHub's migration page.
CodioA hosted learning platform with courseware, coding environments in the browser, auto grading, LMS sign-in and grade passback. Instructors get free access; institutions sign a paid agreement.A complete platform, so you do not build the pieces yourself. Also recommended by GitHub.
CodeGradeAn autograding platform that plugs into your LMS through LTI, passes grades back and has a plagiarism checker.Grading inside the LMS you already use.
Moodle CodeRunner with our APIIf your course is on Moodle, point CodeRunner at the hosted Jobe-compatible API.Grades land in Moodle's gradebook with nothing to install.
OnlineCompiler.io run-tests or stored problemsA grading engine you script against: a student's program, its tests, verdicts and a score back, in 46 languages.Plain input/output assignments, hidden tests, and any course site or LMS plugin you already have. No repositories, rosters or gradebook.

The autograder API page compares autograding software with an API in more detail, and how to build an online judge covers building one on top.

Can it grade a whole class?

Yes. Each test is one run, so 120 students and 3 tests are 360 runs, well inside the Free plan's 100,000 a month. Runs start at no more than 30 a minute on Free, so grading those 360 takes at least 11 minutes; paid plans allow more a minute (see the pricing page). Send the submissions with "async": true, in batches no larger than your account's queue (up to 80 on Free, 200 on Starter, 400 on Pro, 800 on Business waiting at once; a submission past it is refused with 429, so send the next batch as results come in). Each result comes by signed webhook or from GET /api/submissions/{id}/. The asynchronous judging section has the details. Judged submissions are kept for 90 days on Free and longer on paid plans, which is how you re-read a grade later.

Frequently asked questions

Is there a direct replacement for GitHub Classroom?

GitHub named two partners, Codio and Classroom 50, on its migration page. Classroom 50 is the closer match if you want to keep assignment repositories and grading in GitHub Actions. OnlineCompiler.io is not a replacement for repositories, rosters or a gradebook: it replaces the part where a program is run against inputs and expected outputs.

Do students need a GitHub account or an account here?

No account here: your API key submits for them, and their runs count against your plan. Whether they need a GitHub account depends on how you collect their work; submitting from a page you control needs none.

Can I grade pytest or JUnit tests the way Classroom did?

Not as frameworks. A test compares what the program prints with the expected output, and runs have no package installs. To test a function, wrap the student's code in a short driver that calls it and prints the result; a stored problem keeps that driver for you.

Can students see the hidden tests?

No. A hidden test returns only its verdict: no input, output or error. In a stored problem the hidden tests stay on our side and the student's page never receives them. One thing can still be worked out: with weighted scoring, the scores show how much each hidden test weighs, so use equal weights if that matters.

Does it send grades to Moodle, Canvas or Google Classroom?

No. It returns a score in the response or in a signed webhook, and your course site or LMS plugin writes it to the gradebook. The one LMS route we document is Moodle CodeRunner, which calls our Jobe-compatible API.