Pawtograder CLI
The Pawtograder CLI runs course operations from a terminal. It copies assignments between terms, imports and exports rubrics, ingests grading data, performs bulk student-repository maintenance, allocates grading work, and exports course data for analysis. Every command posts to a singlecli edge function on your deployment and authenticates with an API token.
This page is the complete reference. If you are course staff looking for the handful of tasks you run most often, start with the CLI page for course staff.
The published package is
@pawtograder/cli version 0.2.0, licensed GPL-3.0-only. Commands and flags may still change.Install
The CLI is published to npm as@pawtograder/cli. The binary is called pawtograder.
repos commands and assignments copy also need git on your PATH and working SSH access to the course’s GitHub organization; repos copy-after-source-due additionally needs rsync.
Running from the platform repository
Inside a checkout of the platform repository,npm run cli -- runs the same CLI from source:
npm run cli:repos -- is a shortcut for npm run cli -- repos.
Two differences between the two entry points matter in practice:
- The in-repo entry point loads
.env.localand.env. The published binary does not read.envfiles at all, so a variable that works undernpm run clihas no effect on an installedpawtograder. Export it in your shell instead. - A source run reports
0.0.0-devfor--version. That is deliberate, so a run from a working tree is never mistaken for a release.
pawtograder. Substitute npm run cli -- if you are working from the repository.
Invocation
Every command is a group plus an action:pawtograder alone prints help and You must specify a command, then exits 1. A group with no action prints You must specify an action the same way.
Global flags
Two flags work everywhere, and only these two:JSON output
--json prints the raw JSON response instead of the formatted table or plan, and silences every progress message, so stdout is pure JSON that you can pipe into jq. Error text still goes to stderr.
These eleven commands accept --json: classes list, assignments list, flashcards list, rubrics list, rubrics import, submissions list, help-requests list, discussions list, reviews list, reviews assign, and repos list.
Every other command rejects it, including classes show, assignments show, rubrics export, and all three export commands.
Authentication
Create an API token
Open the user menu (your avatar, top right) in the web app and click API Tokens. Under Create New Token, enter a Token Name, choose the CLI (Command Line) or MCP + CLI token type, and click Create Token. The token is shown once, starts withmcp_ whichever type you chose, and is valid for 90 days. Token creation requires an instructor or grader role in at least one class.
Scopes come from the token type. A CLI (Command Line) token carries cli:read and cli:write; an MCP + CLI token carries those plus mcp:read and mcp:write. Read commands need cli:read and write commands need cli:write, so both types work for the whole CLI.
Log in
The CLI checks the token locally before contacting the server. An empty or whitespace-only value gives
No token provided., a token that does not start with mcp_ gives Invalid token format. Pawtograder API tokens start with 'mcp_'., and Ctrl-C at the prompt gives Login cancelled. All three exit 1.
On success, the token and API URL are stored in ~/.pawtograder/credentials.json at mode 0600, inside a ~/.pawtograder/ directory created at mode 0700. The file is written first and the token verified second; if verification fails the CLI restores the previous credentials, or deletes the file if there were none. A failed login never clobbers a working session.
What --url must point at
The gateway is normally api. plus your web hostname. The /functions/v1/cli suffix is optional, because the CLI appends it for you. These two commands store an identical endpoint:
https:// prepended, trailing slashes are stripped, and a value that cannot be parsed as a URL fails with Invalid API URL: <url>. If you are unsure of the origin, copy it out of the API Tokens dialog, which shows the full endpoint form.
--url exists only on login. Every other command reads the stored value, so switching deployments means logging in again.
Check and clear credentials
whoami prints Email, Name, User ID, and the path to the credentials file. logout deletes the credentials file. Neither takes any flag beyond the globals.
whoami exits 0 even when it fails. It prints Not logged in. Run 'pawtograder login' to authenticate. both when no credentials exist and when the stored token is expired or revoked, so do not use it to detect an expired token. logout also always exits 0, including when there was nothing to delete.Tokens in CI
--token is a flag on login, not on individual commands. There is no token environment variable and no way to pass a token to any other command. Run one login step and let the rest of the job read the credentials file:
assignments delete prompts for confirmation on stdin, so pass --force to it in any non-interactive job.
Environment variables
Every variable is optional and read at runtime..env files. Export these in your shell.
Exit codes
The CLI exits0 or 1. There are no other codes.
0: the command succeeded. A cancelledassignments deleteconfirmation also exits 0, because nothing failed.1: any failure, including an unknown or missing flag, a client-side validation failure, an authentication or permission failure, an HTTP error, or an unexpected exception.
assignments copy, repos sync-grade-workflow, and repos copy-after-source-due print their full per-item report and only then exit 1 if anything failed. A non-zero exit from those three means at least one item failed; read the report to see which.Error messages
The 403 text names scopes, but a role check can produce the same status. See Who can run what.
classes
classes list
Lists every class you have a staff role in.classes show
identifier, which can be a class ID, slug, or name. It accepts no flags, including no --json.
assignments
Everywhere below,-c/--class accepts a class ID, slug, or name, and an assignment identifier accepts an assignment ID or slug.
assignments list
assignments show
identifier plus -c/--class (required). No --json.
assignments copy
Copies assignments from one class to another, including rubrics, autograder configuration, self-review settings, and handout and solution repository content. Repository content is pushed with local SSH git rather than server-side, which is why--workdir exists.
Two rules are enforced before anything runs:
- Exactly one of
--assignment,--schedule, or--allis required. Otherwise:Must specify exactly one of: --assignment, --schedule, or --all. --workdiris required unless--skip-reposor--dry-runis set:--workdir is required unless --skip-repos is set (needed to clone source/target repos locally).
--debug turns on timing logs on the server, not locally. For local HTTP logging, set DEBUG=1.(existing, validated/fixed) rather than duplicated, so a timed-out run can simply be repeated.
Schedule CSV format
The CSV is parsed on your machine before any request, and the parsed rows are what gets sent. The header must contain eitherassignment_slug or assignment_title. The date columns release_date, due_date, and latest_due_date are all optional.
YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS) or US form (M/D/YY or MM/DD/YYYY). An empty date cell inherits the source assignment’s value. A UTF-8 byte order mark, which Excel adds, is stripped. Parse errors name the row, as in Row 3 release_date: ….
assignments delete
Deletes an assignment and everything attached to it: GitHub student, handout, and solution repositories, submissions, groups, exceptions and late tokens, review assignments, gradebook columns, and autograder configuration.surveys
surveys copy
Exactly one of
--survey or --all is required: Must specify exactly one of: --survey or --all.
copy is the only surveys action. There is no surveys list.flashcards
flashcards list
-c/--class (required) and --json (default false).
flashcards copy
Exactly one of
--deck or --all is required: Must specify exactly one of: --deck or --all. Note that the short alias here is -d, not -a.
rubrics
Rubrics are imported and exported as YML. All three actions require both-a/--assignment and -c/--class.
rubrics list
--json (default false).
rubrics export
The short alias for
--type is a capital -T. With no -o, the file is written to the current directory as <assignment>-<type>-rubric.yml, using the literal value you passed to --assignment, so -a 123 produces 123-grading-rubric.yml. Unlike the export commands in Exporting student data, this file is written with your normal umask rather than mode 0600.
--strip-ids produces a template, not a round-trip file. Import it and you get new rubric rows rather than updates to the existing ones.rubrics import
Replaces one rubric on an assignment with the contents of a YML file.
The YML is parsed and validated locally before any request. Client-side failures, all exit 1, are
File not found: <path>, Invalid YML: empty or invalid document, Invalid YML: missing 'name' field, Invalid YML: 'parts' must be an array, and a check that rejects NaN or Infinity anywhere in the tree.
--verbose here is a local print flag and has nothing to do with the PAWTOGRADER_VERBOSE environment variable.
submissions
submissions export is covered in Exporting student data.
submissions list
submissions comments import and sync
Both actions read a JSON file of comments and write them onto submissions. They take an identical flag set, and the difference is what happens to comments that are absent from the payload:importonly inserts. Nothing is removed.syncinserts, then soft-deletes rubric checks that are missing from the payload. This is destructive.
Exactly one of
--author-profile-id or --rubric-part-id is required, on both actions. Passing both gives Use only one of --author-profile-id or --rubric-part-id; passing neither gives One of --author-profile-id or --rubric-part-id is required. Because the rule is enforced in the handler rather than by the parser, --help shows both as optional.File not found: <path> is checked locally before the request. Both file shapes are normalized on your machine, so a legacy batch-results.json and a manifest behave the same from here on.
submissions artifacts import
Uploads artifact blobs, such as coverage reports, onto existing submissions.
The manifest is a JSON object with a non-empty
artifacts array:
content_base64 or content_file. A relative content_file path is resolved against the manifest’s own directory and base64-encoded locally. Missing pieces fail before upload with Manifest must include non-empty artifacts array, content_file not found: <path>, or Artifact <name> needs content_base64 or content_file.
help-requests
help-requests list
The default
--limit is 100 here, unlike the 1000 used by submissions list and reviews list. When a page is truncated, the CLI prints the exact --offset to continue with. Columns are ID, Queue, Status, Student, Assignee, Created, and Request, with timestamps in the class’s time zone.
help-requests close
Sets a help request to a terminal status. This is the only way to set theclosed status; nothing in the web interface does it.
Output is
Help request <id>: <previous status> -> <new status>, followed by Resolution: and Notes: lines when you supplied them.
Four behaviors are worth knowing before you script this:
- The default status is
closed, so a bareclose --id 42closes the request and records no resolution status or notes. - The resolution system message is only posted when a resolution status is set. Closing without
--resolution-statusproduces no message in the request’s chat. - Closing also records one help-activity row per participating student. If that bookkeeping fails, the request is still closed and the CLI tells you to re-run with
--forceto retry. Participants already recorded are not duplicated, so--forcedoubles as the retry for the activity backfill. - Forcing a close on a request with a live video call leaves the call running, and the End Call button is disabled from then on. The CLI warns when this happens.
--force, a request that already has a terminal status, or that has a live video call, fails with an API error and exit 1.
discussions
discussions list
-c/--class (required) and --json (default false). list is the only discussions action.
reviews
reviews list
--rubric has no default here, so all rounds are listed. On reviews assign it defaults to grading.
reviews assign
Creates review assignments, either balanced round-robin across the grader pool or from an explicit manifest.--grader takes profile IDs, not emails or names, and is repeated once per grader. Non-submitters are excluded by default, matching the web bulk-assign page.
--file conflicts with --grader, --by-part, and --include-non-submitters: --file supplies explicit assignments; it cannot be combined with --grader, --by-part, or --include-non-submitters. It works fine alongside --rubric, --due-date, --dry-run, and --json. The manifest is a JSON array:
How --due-date is interpreted
A string that already ends in Z or an offset such as -05:00 is passed through untouched. Anything else is interpreted by the server in the class’s time zone, and a bare date means the end of that day. For a New York course, --due-date 2026-09-15 is 23:59:59 Eastern, not midnight UTC. Append a time (2026-09-15T17:00) for a specific hour.
What the allocation guarantees
Re-running is safe. Existing assignments are reused rather than duplicated, already-assigned work is left alone, and an assignment covering the whole rubric counts as covering each of its parts, so--by-part will not re-deal work someone already holds.
Allocation honors grading_conflicts and never assigns anyone their own submission. That applies to --file entries too, which are rejected rather than written.
The report distinguishes new assignments from retargeted_stale ones, where an existing assignment was repointed at a newer submission and so is not new load on that grader. It separately reports skipped_already_assigned, excluded stubs, excluded dropped students, stale collisions, and any submission left with no eligible grader after conflicts.
repos
repos list
-c/--class (required), -a/--assignment (required), and --json (default false).
repos sync-grade-workflow
Copies.github/workflows/grade.yml from the handout repository into every student repository, then commits and pushes.
The
--concurrency default is 2 here, not the 4 used by assignments copy. Per-repository failures are reported and set the exit code to 1 after the run finishes.
repos copy-after-source-due
Copies each student’s source-assignment repository tree into their target-assignment repository, once the source assignment’s due date has passed. This suits sequential assignments where students build on earlier work.
Per-repository failures are reported and set the exit code to 1 after the run finishes.
Exporting student data
Three commands write course data to disk:assessment export, submissions export, and assessment deanonymize. All three are gated by the same --identity contract, and all three can write student personal data. Read this section before running any of them.
The --identity contract
assessment export and submissions export both take --identity, with three modes:
Validation happens before any network call, and each failure exits 1:
--identity raw requires --i-understand-pii to acknowledge that real student data will be written to disk--identity hash requires --salt (any string of length >= 16)--salt must be at least 16 characters
hash dump is not something its recipient can de-anonymize. Only an instructor, running assessment deanonymize against the same deployment with the same salt, can map tokens back to students.
Repository names and commit SHAs are tokenized in hash and opaque mode, because a repository name usually embeds a student’s GitHub handle and a SHA can be looked up on GitHub. In raw mode they are real.
A few identifiers are raw in every mode because they are course metadata rather than student identity: assignment, rubric, and gradebook column IDs and names, and the discussion_thread_id and error_pin_id on engagement rows. The last two are joinable back to discussion content that identifiable students wrote, so treat them as identifying even in a tokenized dump.
Who is allowed to run these
Output locations and file modes
All three commands write into the current working directory by default, under a timestamped name, and-o/--output overrides that. Directories are created at mode 0700 and files at mode 0600. Setting those modes is best-effort and a no-op on Windows.
Integrity checks and partial output
Both export commands stream NDJSON and verify what they received. Every stream must end with an{end} marker and the server’s reported counts must match the rows received, or the command aborts with Stream count mismatch for <field>: server reported X but received Y. The dump is incomplete; do not use it for analysis. or Server stream ended without an {end} marker — the dump may be incomplete. Both exit 1.
These aborts exist so that an incomplete dump is not mistaken for a complete one.
Transient stream failures, such as a dropped socket, an HTTP 503 or 504, or an edge-function CPU limit, are retried up to three attempts with exponential backoff. Authentication, validation, and count-mismatch errors are not retried. Retries are safe because tokens derive from the run’s salt, so a retried call returns identical data.
assessment deanonymize behaves differently: it buffers the whole roster and writes the CSV only after the row count checks out, so a failed run leaves no partial file.
assessment export
Exports assessment data for a class: subjects, sections, assignments, rubrics, autograder configuration, submissions, scores, tests, hints, engagement, and gradebook cells.
Two combination rules apply:
--skip-gradebook cannot be combined with --gradebook-column, and --instructor-build-output-from-sentinel requires --with-instructor-build-output.
The output tree is:
Omitting
--gradebook-column exports every column, including instructor-only ones. The default is maximal, not minimal. Use --skip-gradebook to leave the gradebook out.dump_id, a fresh UUID that is sent with every request and recorded in manifest.json, which is useful when correlating an export with server logs.
An -a or --gradebook-column selector that matches nothing produces a warning rather than an error, so a mistyped glob quietly exports less than you expected. Read the warnings before you trust the dump.
submissions export
Exports submission metadata and, in JSON format, the source files themselves.
The layout depends on how many assignments matched. Exactly one match writes
<output>/<slug>/; more than one adds a level, <output>/assignments/<slug>/. Script against the root manifest.json, which lists the assignments, rather than assuming a depth.
--format changes what is on disk, not only its shape. The CSV columns are fixed: submissions.csv holds submission,subject,group,ordinal,sha,is_active,created_at,repository,has_final_review, and files.csv holds submission,name,is_binary,file_size,mime_type,binary_omitted. Neither contains source code. If the code is the point of the export, use the default --format json.
An -a selector matching nothing is a hard error here, No assignments matched the given selectors, unlike assessment export, which warns and continues.
The root
manifest.json records include_files, exclude_files, all_submissions, and with_binary only when you set them, so it is a faithful record of the flags a dump was produced with.assessment deanonymize
Writes a CSV mapping the subject tokens in ahash export back to student identifiers.
The CSV header is exactly:
assessment export in hash mode holds section tokens, so this file is the join key for both.
Validation before any request: omitting --i-understand-pii gives --i-understand-pii is required — this command writes real student names, emails, and SIS ids to disk, and a short salt gives --salt must be at least 16 characters (must match the salt from the original export run).
Who can run what
Issuing any token requires an instructor or grader role in at least one class. Beyond that, every command checks your role in the class you named. Two commands are stricter than the rest:admin is deliberately not a CLI role. A site administrator with no instructor or grader role in a class cannot run CLI commands against it.
Troubleshooting
Unknown argument
The flag does not exist on the command you typed. Strict mode makes this fatal rather than ignoring it. Runpawtograder <group> <action> --help and compare, and check the tables above: several flags that look global, including --json and --dry-run, are per command.
Authentication and permission failures
Runpawtograder login again first, since a token lasts 90 days and whoami cannot tell you that yours has expired. If a 403 persists after a fresh login, check whether the command is one of the two instructor-only ones listed above rather than assuming a scope problem: the 403 text names scopes, but a role check produces the same status. help-requests close is the exception, because a non-instructor gets a not-found error there instead.
Timeouts on long operations
Copying many assignments, importing large artifacts, and exporting a whole class can all outrun a gateway timeout.--batch-size helps more than a longer timeout. For assignment copies, re-running is safe: existing target assignments are validated and fixed rather than duplicated. For exports, the CLI already retries transient stream failures three times on its own.
Git and SSH failures
assignments copy and both repos commands run git locally over SSH. Confirm SSH access with ssh -T git@github.com, confirm you are a member of the course’s GitHub organization, and confirm --workdir exists and has room for a clone of every student repository.
If clones fail intermittently, lower --concurrency to 2 or 1 and add --delay-ms 1000 to space out the batches. Connection resets during git operations are retried automatically.
Getting help
https://pawtograder.com.