How open edX external graders with XQueue and Codejail fit together
XQueue is the supported path for external graders in Open edX, and Codejail is the sandbox for running untrusted code. Both are configured through Tutor plugins from eduNEXT, the Xqueue plugin and the Codejail plugin, which appear in the Tutor plugins index. Picture a course team that wants automated grading of code exercises and a security owner who must approve running learner code on infrastructure the organization controls. The course team asks whether feedback comes back quickly and accurately. The security owner asks what the submitted code can reach and who holds the privileges that run it. This section gives both roles one shared picture before either signs off.
At a high level, a learner submits code to a problem, and the LMS stores the submission and publishes it. With the event bus enabled, which is the mode the Open edX documentation describes, submissions move as events. An XQueue watcher consumes the event, passes the code to the grader, and posts the result back through the edx-submissions API so the LMS can record the score. The watcher is the component that connects the event flow to the grading step, so its placement and its identity are central to the design. According to the Open edX documentation, the watcher authenticates as a Django superuser in the xqueue group. That is a privileged identity, and the security review should start there.
The security boundary has to answer four questions: which component executes learner code, where that component runs, what network and file access it has, and which identity it uses. The Codejail plugin is intended to run untrusted code in secure sandboxes, but the design must confirm where the sandbox sits relative to the watcher and the LMS. Ask the implementation partner to document whether the watcher and the sandbox share a host, cluster or network segment, and whether the event bus broker and the XQueue endpoints are reachable only from private networks. Verify that a grader submission cannot reach the LMS database, the watcher's credentials or the wider internet unless a specific grader needs that access.
Plan the work in an order that gives the security owner evidence before the course team relies on grading. The platform team enables the event bus and deploys the Xqueue and Codejail plugins. The watcher account is scoped and its credentials are stored in a secrets system the organization already operates. A sample exercise with known-good and known-bad submissions is run end to end, and the watcher logs are reviewed for the expected flow. Before scope is agreed, confirm with the plugin maintainers which Tutor and Open edX release combinations they support, and ask for the test plan, the configuration list and the operating runbook in writing.
What you need in place before enabling open edX external graders
External graders only work when the platform underneath them is ready. The items below cover the base deployment, the event bus, the plugins, a test course and the network and capacity the grader needs, so the XQueue and Codejail work starts from a known state.
A working Open edX base deployment
Plan on a running Open edX instance, most likely built with Tutor, which the edx-platform maintainers recommend over bare-metal installation. Development of edx-platform requires Python 3.12, Node.js, MySQL 8.0, MongoDB 7.x and Memcached, so confirm which versions the release you run lists. Check that the LMS and Studio both load before adding any grading components.
Release and plugin version alignment
Open edX supports only the latest named release, so the Codejail and Xqueue plugin versions must match the release you run. Open edX Verawood is the current named release, published on 30 July 2026, and the next releases are Willow and Xylon. Confirm plugin compatibility with the eduNEXT plugin documentation before you install anything.
Event bus enablement
Event bus based XQueue submissions depend on the LMS publishing and storing submission events, so the event bus must be enabled and working first. Confirm the event bus settings in your Tutor configuration and check that events are flowing before you add a watcher. Without this step, submissions never reach the grader.
Codejail and Xqueue plugin install path
The Tutor plugins index lists a Codejail plugin by eduNEXT for running untrusted code in secure sandboxes, and an Xqueue plugin by eduNEXT for external grading. Decide where each plugin is installed, record the Tutor version and plugin versions in your configuration repository, and test the install in a non-production environment first.
A test course with a code problem
Build a small test course with one code problem that uses an external grader, so you can submit a known answer and see a known result. Include a correct solution, an incorrect one and a solution that times out or fails to compile. This course becomes the acceptance test for the whole chain.
Network paths and worker capacity
Map the network paths between the LMS, the XQueue watcher and the sandboxed execution environment, and confirm which ones must stay internal. Size worker capacity against expected concurrent submissions, and ask the plugin maintainers which resource limits they recommend. Record these values before you schedule a pilot with learners.
What happens to a learner submission on the event bus
Once the event bus is enabled, every learner submission to an external grader follows the same path, and each hop is a place where it can stall. The LMS stores the submission and publishes it as an event on the bus. The learner's submission is recorded before any code runs, so the LMS does not wait on a grader in the request itself. An XQueue watcher then consumes that event and takes responsibility for grading it. Your team should trace one test submission through each of these stages, from the LMS record to the recorded score, before any learner uses the course. Confirm the exact queue and topic names in the official how-to, because the watcher configuration must match them exactly.
The watcher hands the learner's code to the grader, which executes it inside Codejail, the sandbox that runs untrusted code separately from the rest of the grading process. The Tutor plugins index lists a Codejail plugin by eduNEXT for enabling this execution, and an Xqueue plugin by eduNEXT for external grading. Before you rely on either, confirm which plugin versions match your Open edX release and how the sandbox is configured in your environment. The watcher authenticates to the platform as a Django superuser in the xqueue group, which means its credentials carry broad privileges. Treat that account as a high-value identity and decide in advance where its credentials are stored and who can read them.
After grading finishes, the watcher posts the result back through the edx-submissions API, and the LMS records the score against the learner's submission. This final hop is where a failure is easiest to miss. Because the LMS stores and publishes the submission before any watcher reads it, a submission can sit in the record as received while no watcher is consuming the event. The learner sees a pending result, the LMS shows no error, and the fault sits on the consumer side of the bus. Plan monitoring for the watcher itself, covering whether it is running, whether it is connected to the bus, and how many events are waiting for it. Alert on events waiting for a consumer so that a stalled queue does not surface first through learner complaints.
Before go-live, your team should answer questions that the documentation leaves to the official how-to and to your own environment. Which broker carries the events in your deployment, and which consumer group does the watcher join? What happens to a message when grading raises an error: is it redelivered, retried or parked? How does a failed grade appear to the learner and to course staff? Who owns the watcher service, its restart behavior and its credentials? Record the answers in the runbook for the course team. Then run a failing test submission on purpose, so the failure path is tested before a learner reaches it.
How to configure XQueue watchers and Codejail in sequence
This sequence takes an Open edX deployment from a learner code submission to a graded score, using XQueue external graders and Codejail sandboxes. Each step depends on the one before it, so the order matters: the event bus must carry submissions before the watcher can consume them, and the sandbox must run code correctly before the grader is trusted. The sequence ends with a test problem whose score appears in the LMS.
Enable the event bus in the deployment
The platform operator enables the event bus in the Open edX deployment, using the official how-to for the setting names and values. With the event bus on, the LMS stores each XQueue submission and publishes it as an event for consumers to pick up. Confirm the setup by sending a test submission and observing the published event on the bus. Do not move on until that event is visible, because every later step depends on it.
Install and test the Codejail sandbox
Install the Codejail plugin by eduNEXT, listed in the Tutor plugins index, and follow its setup steps for the sandbox service. Run a small, known program through the sandbox to confirm it executes and returns output. Then review the restrictions the plugin documents, such as limits on file system, network and resource access, and have your security team verify them in your environment. This step is done when the sandbox run returns the expected output and the restrictions match your review.
Install and configure the Xqueue plugin
Install the Xqueue plugin by eduNEXT from the Tutor plugins index. Set its values so the LMS points to the XQueue service and the event bus, using the names and values in the official how-to. Store connection credentials in your secrets store and keep them out of plain configuration files. Redeploy, then check the LMS and XQueue service logs for configuration errors. The step is done when both services start cleanly with the new settings.
Start the XQueue watcher with its documented identity
Start the XQueue watcher using the identity the Open edX docs describe. The docs state that the watcher authenticates as a Django superuser in the xqueue group, so review what that account can reach in your environment and restrict the network access around it. The watcher consumes submissions from the event bus and posts results through the edx-submissions API. Check the watcher log for a line recording that it consumed a submission event after you send a test. Until that line appears, the watcher is not connected to the bus.
Create a test problem that submits code
In Studio, create a test problem configured to send learner code to the external grader, using the problem settings and grader fields named in the official how-to. Use a short program with a known expected result, and keep the grader and its test cases in a reviewed location. Submit a working solution as a learner would. The step is done when the LMS shows the submission as pending, the submission event appears on the bus, and the watcher log records its consumption.
Confirm the score appears in the LMS
Wait for the watcher to post the result, then confirm that the score for the test submission appears in the LMS for the learner account used in the previous step. Compare the score with the expected result you set for the test program. If the score is missing, check the watcher log for a posting error before changing grader settings. Once the correct score displays, submit an incorrect solution to confirm that a failing result is also recorded and shown. The step is done when both results display as expected.
Which submission fields and identities map between the LMS and the grader
This table traces each data item across the LMS, the event bus, the XQueue watcher and the edx-submissions API for an event bus based XQueue submission. Read it as a checklist: for every row, confirm the field names and identities against the official how-to before you write grader code, because names can change between releases. The main takeaway is that the watcher's identity and the submission identifiers are the two places where a mismatch most often breaks grading silently.
| Data item | Where it originates | Where it is consumed | What to verify |
|---|---|---|---|
| Learner submission and identifiers | LMS, when a learner submits a code problem (field names to be confirmed against the official how-to) | Event bus consumer, the XQueue watcher, and the edx-submissions record | Submission identifier is stored once and reused in the returned result, so the grader result attaches to the right attempt |
| Grader payload | LMS, published to the event bus as the submission is stored (field names to be confirmed) | XQueue watcher, which passes the learner code and test input to the grader runtime | Payload carries the learner code, the problem's test inputs and the grading settings without exposing answer keys to the learner |
| Expected output and test cases | Course author, stored with the problem definition in the course content | Grader runtime inside the Codejail sandbox, which compares learner output to expected output | Expected output is available to the grader only, and a known-good solution scores full marks in a test course |
| Watcher identity in the xqueue group | Watcher service account, configured as a Django superuser in the xqueue group per the official how-to | edx-submissions API calls that post results back to the LMS | Account exists, is in the expected group and can post results, and its credentials are stored outside source control |
| Returned score and feedback | XQueue watcher, after the sandbox run completes | edx-submissions API, which records the score and feedback for the attempt | Score scale matches the problem's maximum, feedback text is shown to the learner and errors from the sandbox are reported rather than recorded as zero |
| Record the LMS stores | LMS and edx-submissions, updated when the result is posted | Learner progress, grade book and instructor views for the problem | Grade appears on the learner's progress page, is visible to staff and is correct after a re-submission |
| Timeout and error states | Sandbox or watcher, when execution fails or exceeds limits (confirm limit settings in your Codejail configuration) | LMS submission status and staff logs | Failed runs produce an identifiable status instead of a blank result, and staff can trace the failure to a submission identifier |
Where the Codejail sandbox boundary sits and what the watcher can reach
These six points set the boundary around learner code and the watcher account that returns grades to the LMS. Read them as one design: the sandbox limits what learner code can do, while the watcher's privileges, network paths and logs determine what a compromise would reach. Confirm each point with the Codejail plugin documentation and your security team before any learner submits code.
Sandbox isolation and its gaps
Learner code should run only inside the Codejail sandbox, never directly on the grader host or inside the LMS process. Confirm which isolation mechanism the plugin uses, which system calls, files and devices it restricts, and what it leaves to the host operating system. Treat the sandbox as one control among several, and ask the plugin maintainers for their documented threat model before you rely on it for untrusted code.
Watcher superuser account
The XQueue watcher authenticates as a Django superuser in the xqueue group, so anyone who obtains its credentials gains broad rights in the LMS. Plan narrow network reach so the watcher can call only the LMS and submission endpoints it needs. Store its credentials in a secrets manager, rotate them on a schedule your security team sets, and alert on logins and API calls that fall outside normal grading traffic.
Resource limits per run
Each grading run needs limits on CPU time, memory, wall-clock time, process count and output size, or a single submission can starve the grader pool. Ask the Codejail and XQueue plugins which of these limits they expose and which defaults they ship with. Test a submission that loops forever, allocates memory until it fails and writes large output, then confirm the worker recovers and reports the failure.
Egress from grader hosts
Learner code should have no path to the internet, internal services or cloud metadata endpoints from a grader host. Block outbound traffic by default at the host or network layer, allowing only the paths the watcher needs to reach the LMS. Verify this with a test submission that attempts a DNS lookup and an outbound HTTP request, and confirm that both attempts fail and appear in your logs.
Logging of learner code
Submitted code is personal data when it comes from a named learner, and it may contain answers, secrets or third-party data pasted by mistake. Decide which parts are logged (the submission, the output, the grader verdict), where they are stored, who can read them and how long they are kept. Confirm that grader logs never capture the watcher's credentials or the full environment of the worker process.
Separation from the LMS database
Grader workers should not connect to the LMS database directly. In the event bus flow, the watcher posts results through the edx-submissions API, so workers need API access and no database credentials. Confirm this in the worker configuration, restrict database access to the LMS and its own service accounts, and verify that a compromised grader cannot read other learners' records.
How to test a graded code submission end to end
Test the whole chain before any learner submits code. The chain runs from the LMS problem, through the event bus, the XQueue watcher and the Codejail sandbox, to the result stored through edx-submissions and the score in the gradebook. A small set of cases covers the failure modes that matter. Write the expected outcome of each case down before you run it, so a pass means the system matched a target you set in advance, not a target you adjusted after seeing the output. Keep the test course separate from any live course so test scores never reach real learner records.
Start with a known-good submission and a known-bad one. The known-good answer should receive full credit, and the score should appear on the learner's progress page and in the instructor gradebook. The known-bad answer should receive the score your problem definition assigns, usually zero or partial credit. The course team writes the test cases, and a reviewer who can judge expected output checks each one independently. Ask that reviewer to cover edge cases such as empty input, extra whitespace and unusual formatting, because a grader that passes the obvious cases can still fail these in production.
Next, submit code that loops forever and code that uses far more memory or time than the problem allows. Codejail should stop both within the limits configured in your deployment. Confirm those limits in your own environment rather than assuming defaults, and check the eduNEXT Codejail plugin documentation for which controls it exposes. The learner should see a clear error, and the watcher should keep consuming the queue. Submit a normal answer straight afterward. If it is graded correctly, the sandbox recovered and the watcher was not left blocked by the hostile submission.
Then test recovery. Stop the XQueue watcher, submit an answer as a learner, and confirm the submission is stored and shown as pending rather than lost or silently dropped. Start the watcher and confirm the queued submission is graded and its score appears once, with no duplicate entries in the gradebook. During this test, confirm which service account the watcher uses. It authenticates as a Django superuser in the xqueue group, so verify that its credentials are stored securely, that access is limited to what the watcher needs, and that rotation is part of your operating procedure.
Finally, check the logs. Search the LMS, watcher and sandbox logs for the source code and output strings from your test submissions. Only the fields your logging policy permits should appear, such as submission identifiers, timestamps and scores. Confirm the exact list with the person who owns the policy, then record the results with the test date and the reviewer's name. Repeat this check after every configuration change, since a debug setting can start writing learner code to logs without any visible error.
Why submissions stall, fail or score incorrectly
This table lists the failures teams most often hit when an XQueue external grader does not behave as expected, with the likely cause, a check to run and a fix for each. Work through the rows in order, since an unpublished event or a disabled event bus hides every later symptom. Confirm each cause and setting against the official Open edX XQueue documentation and your own Tutor configuration before changing anything in production.
| Symptom | Likely cause | Check | Fix |
|---|---|---|---|
| Submissions are accepted by the LMS but never reach the watcher | The event bus is not enabled for the XQueue flow, so the LMS does not publish the submission as an event | Confirm the event bus settings in the LMS and that the XQueue event is enabled, following the official event bus based XQueue guide | Enable the event bus and the XQueue submission event in your configuration, redeploy the LMS and submit a test answer |
| The event is sent but nothing is consumed | The event bus is enabled but the event is not published to the topic the watcher subscribes to, or the topic names do not match | Compare the published topic and consumer group names in the LMS and watcher settings and check the bus for messages on the expected topic | Align topic and group names with the documented values, then restart the watcher so it resubscribes |
| The watcher starts but reports an authentication or permission error | The watcher is not running as the Django superuser in the xqueue group that the edx-submissions API expects, or that account was not created | Confirm the xqueue group membership and superuser flag for the watcher account in the LMS database, and check the watcher's credentials | Create or correct the superuser account in the xqueue group as described in the docs, then store its credentials in the watcher configuration |
| Sandbox runs time out or the grader process is killed | Codejail limits for time or memory are lower than the grader needs, or the grader code loops or allocates heavily on learner input | Review the Codejail time and memory limits and the grader logs for the killed run, and test the same input locally | Raise limits only after confirming the grader logic is sound, and add timeouts and input validation to the grader itself |
| Sandboxed code fails with permission or import errors | The sandbox user or allowed libraries are not set up for the grader, so the code cannot run or read its files | Check the Codejail sandbox user, the permitted Python packages and file permissions in the grader container | Install the required packages inside the sandbox environment and grant read access only to the grader files it needs |
| Grader returns a result but the learner sees an error or zero score | The grader output does not match the JSON structure XQueue expects, such as missing score, message or correctness fields | Print the raw grader response for a sample submission and compare it with the output format in the official XQueue documentation | Return the documented fields with the correct types, and add a test that fails when the format changes |
| Scores are calculated but do not appear in the gradebook | The score fails to post through the edx-submissions API, often due to an invalid submission identifier, a permission problem or a version mismatch between the watcher and the platform | Check the watcher logs for the API response and confirm the edx-submissions version matches your Open edX release | Correct the identifier or permission problem, align package versions with your release and resubmit the affected answers |
| Failures appear only when more than one LMS or worker node is running | A shared Django cache backend such as Redis or Memcached is not configured consistently, or the watcher and sandbox run on nodes that cannot reach each other | Confirm every LMS and worker node points to the same cache, and check which nodes run the watcher and where results are read | Configure one shared cache backend for all nodes, and place watchers and sandboxes where the event bus and the platform can both reach them |
Questions teams ask before running external graders
What drives the cost of an external grader setup for Open edX?
The cost is driven by how many grader types you need, how much custom grading logic each one contains, and how many environments must be built and verified. The main cost components are planning and architecture, configuring the event bus and XQueue watchers, building or adapting grader containers, setting up Codejail sandboxes, security review, and handover to your operators. Most of this is one-time work. Recurring costs come from keeping watchers, sandbox images and Tutor versions current through upgrades, which is usually a separate support arrangement. Ask any provider for a written scope listing each grader, each sandbox policy, each environment, and what is included after go-live, so you can compare quotes on the same basis.
How long does it take to set up XQueue graders with Codejail?
Duration depends mainly on the number of grading problem types, the languages your learners submit, and how quickly your internal team supplies test cases and approvals. Other factors include whether the event bus is already enabled in your deployment, how many environments you need (development, staging and production), and how many rounds of security review your policies require. Teams that already run Tutor and have a working course with test submissions usually move faster than teams starting from a bare installation. Ask for a timeline with milestones tied to named inputs from your side, and a stated assumption about how many review cycles are included.
What does our internal team need to provide for external grading?
Your team needs to supply the grading requirements and the material that defines correct behavior. In practice that means test cases with expected outputs, sample submissions that include known-bad code, the allowed languages and libraries, time and memory limits, and the course content that links to each problem. Your security team must review the sandbox boundary, network egress, secrets handling and logging before production use. You also need a named operator who receives watcher and queue alerts and can restart services or roll back a grader image. Put each of these inputs in the written scope with an owner and a due date.
Can our own engineers build this using the official docs and Tutor plugins?
Yes, a capable team can build this without a partner, because the Open edX documentation describes how to enable event bus based XQueue submissions and test them, and the Tutor plugins index lists an Xqueue plugin and a Codejail plugin, both from eduNEXT. The harder parts are the ones the documentation does not decide for you: how your grader containers are written and tested, how the sandbox is hardened for untrusted code, how watcher credentials are scoped, and how failures are monitored. Confirm plugin compatibility with your Tutor and Open edX release before you start, because only the latest named release is supported. Teams that choose to build in-house should still plan a security review and an on-call rota.
Why does running untrusted learner code need a separate security review?
Learner code is untrusted input, and a grader that executes it can be abused to read secrets, reach internal networks or exhaust resources if the sandbox boundary is weak. The review should confirm what the Codejail sandbox can access, whether outbound network access is blocked, how temporary files and containers are cleaned up, and which service account the watcher runs as. The watcher authenticates as a Django superuser in the xqueue group, so its credentials and permissions deserve close attention. Ask the provider to document the threat model, the test cases used to probe the boundary, and the residual risks you must accept or mitigate.
What should we ask for in a written scope for external graders?
Ask for a scope that names each grader and problem type, the languages and limits for each, the Tutor and Open edX release it targets, and the environments covered. Require a description of the event bus configuration, the watcher deployment, the sandbox policy and the security tests run against it. The scope should list your inputs, including test cases and reviewers, and state what happens if they are late. Also ask for the handover package, the runbook for the on-call operator, the monitoring you will receive, and whether post-launch support or upgrades are included or handled through a separate retainer. Fixed-scope agreements make these points easier to check.
Does DeployOpen certify that our graders are secure?
No. DeployOpen plans, implements, hardens and tests the external grader setup inside your infrastructure, and prepares the systems, controls and evidence your auditors will review. It does not issue audit reports, attestations or certifications, which come from independent auditors or certification bodies. DeployOpen also runs security testing that can be scoped to the sandbox and the grader path. Ask for the test results and the evidence pack, and agree in writing which findings are fixed within the scope and which are handed back to your team to decide.
How DeployOpen scopes and delivers open edX graders
DeployOpen plans, builds and documents Open edX external graders with XQueue and Codejail inside infrastructure you control. Each engagement starts from a written scope that sets out the grader flow, the sandbox boundary and the operating model before any work begins.
How we scope
- Map which course assignments need external grading and which submission types each grader must accept
- Confirm the Open edX release and Tutor version the platform will run, since grader setup depends on the chosen release
- Define the security boundary for untrusted learner code, including sandbox placement, network access and resource limits
- Agree the written scope, deliverables and timeline with you before work starts, as engagements are fixed-scope
How we build
- Enable the event bus based XQueue submission flow and verify that the LMS stores and publishes each submission
- Configure XQueue watchers to consume submissions and post results through the edx-submissions API with the required service identity
- Set up Codejail sandboxes for untrusted code, using the eduNEXT Codejail and Xqueue plugins listed in the Tutor plugins index
- Test grading end to end with sample courses, including failure paths for timeouts, malformed code and watcher restarts
How we hand over
- Document the submission flow, watcher configuration and sandbox boundary so your team can trace a grade from start to finish
- Record the watcher identity, its group membership and the permissions it holds, with a review schedule for them
- Provide runbooks for restarting watchers, rotating credentials and checking queue health after platform upgrades
- Walk your operations and course teams through the test results and the known limits of the sandbox design
How we support
- Offer ongoing support, upgrades and testing of the grader stack as a retainer after handover
- Review grader configuration again when you move to a new named Open edX release or change Tutor plugin versions
- Ask for a quote based on the written scope, which sets out what the retainer covers for your grader workload
- Confirm with your team which parts of grading, code execution and data retention stay in your own operations

