theonedev/onedev · error · ExplicitException

Job shell not ready

Error message

Job shell not ready

What it means

Thrown by DefaultJobService.openShell when a terminal session is opened for a running build, but on the cluster server that owns the job server, no JobRunnable is registered for the job token. It means the build's executing process is not (yet or anymore) able to host an interactive shell. The library throws it as an ExplicitException so the UI shows the message directly.

Source

Thrown at server-core/src/main/java/io/onedev/server/job/DefaultJobService.java:953

		}
	}

	@Sessional
	@Override
	public JobShell openShell(Build build, JobTerminal terminal) {
		var jobContext = getJobContext(build.getId());
		if (jobContext != null) {
			var jobToken = jobContext.getJobToken();
			var shellServer = jobServers.get(jobToken);
			if (shellServer != null) {
				clusterService.runOnServer(shellServer, () -> {
					var innerJobContext = getJobContext(jobToken, true);
					var jobRunnable = jobRunnables.get(innerJobContext.getJobToken());
					if (jobRunnable != null) {
						var shell = jobRunnable.openShell(innerJobContext, terminal);
						jobShells.put(terminal.getSessionId(), shell);
					} else {
						throw new ExplicitException("Job shell not ready");
					}
					return null;
				});

				return new JobShell(build.getId(), terminal.getSessionId()) {

					private static final long serialVersionUID = 1L;

					@Override
					public void writeToStdin(String data) {
						clusterService.runOnServer(shellServer, () -> {
							try {
								var shell = jobShells.get(terminal.getSessionId());
								if (shell != null)
									shell.writeToStdin(data);
							} catch (Throwable e) {
								logException("Error sending shell input", e);
							}

View on GitHub (pinned to d44925c47c)

Solutions

  1. Wait until the build reaches a running state with an active step, then retry opening the terminal
  2. Refresh the build page and reopen the terminal (stale JobContext/jobServer entries may be gone)
  3. Check the job executor/logs to confirm the job process is actually alive
  4. If using a cluster, verify the node hosting the job is healthy

Example fix

// before: opening shell immediately after clicking build
var shell = jobService.openShell(build, terminal);
// after: only open when build is running
if (build.getStatus() == BuildStatus.RUNNING)
    var shell = jobService.openShell(build, terminal);
else
    throw new IllegalStateException("Build is not running; terminal unavailable");
Defensive patterns

Strategy: retry

Validate before calling

// only attempt terminal when build is actively running
if (build.getStatus() != BuildStatus.RUNNING)
    throw new IllegalStateException("Terminal requires a running build");

Type guard

boolean shellAvailable = build.getStatus() == BuildStatus.RUNNING;

Try / catch

try {
    var shell = jobService.openShell(build, terminal);
} catch (ExplicitException e) {
    // show 'terminal not ready, retry shortly' message
}

Prevention

When it happens

Trigger: Calling openShell(build, terminal) (e.g. user clicks the terminal button on a build) when jobRunnables.get(jobToken) returns null on the shell server — typically in the window between job server registration and job runnable start, or after the job executor process died.

Common situations: Clicking 'Open terminal' right as the build starts or finishes; the job runner container crashed; build is queued/pending but a stale JobContext still exists; multi-node cluster race where the shell server differs from the running node.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/26c06872776820f3. Report an issue: GitHub.