﻿---
title: Job Control in Nushell
date: 2026-01-19
tags:
  - Shell
  - Nushell
  - Asynchronous
  - Workflow
excerpt: "The Job Control chapter of Effective Shell covers task control in Bash. This post shows how the same ideas work in Nushell."
updated: 2026-07-08 21:17:39
lang: en
i18n:
  cn: /job_control
  translation: 2
---

## Job Control

For what job control is, see [Effective Shell/Job-Control#What-is-job-control](https://effective-shell.com/part-2-core-skills/job-control#what-is-job-control).

In short, it is **the ability to move tasks between the foreground and the background**.

### Problems It Solves

1. **Freeing the terminal**: move a long-running task into the background so the command line doesn't get stuck, letting you multitask in a single window.
2. **Suspend and resume**: pause the current program (e.g. Vim) to handle something urgent, then seamlessly return to where you were.
   P.S: if you use Tmux/Zellij, a floating pane also works for handling interruptions, see [[tmux_floating_pane|my post on that]]
3. **Task management**: list every task, know exactly what is running, and kill any of them at will.

## Job Management Overview

> [!TIP]- for UNIX Shell Users
>
> - Append `&` to a command to run it in the background
> - Use `{bash} jobs` to list background commands
> - Press `Ctrl-Z` to **suspend** a command and send it to the background
> - Use `fg` to bring a background command to the foreground

Nushell's built-in `{nu} job` command provides functionality similar to UNIX shells. Run `{nu} help job` to see how it works.

You could say Nushell and PowerShell commands are quite self-explanatory. UNIX shells, by contrast, lean toward brevity, relying heavily on symbols and abbreviations that you have to memorize, which makes them hard for beginners to decipher.

```nu
$ help job
Various commands for working with background jobs.

You must use one of the following subcommands. Using this command as-is will only produce this help message.

Usage:
  > job

Subcommands:
  job flush - Clear this job's mailbox.
  job id - Get id of current job.
  job kill - Kill a background job.
  job list - List background jobs.
  job recv - Read a message from the mailbox.
  job send - Send a message to the mailbox of a job.
  job spawn - Spawn a background job and retrieve its ID.
  job tag - Add a description tag to a background job.
  job unfreeze - Unfreeze a frozen process job in foreground.

Flags:
  -h, --help: Display the help message for this command

Input/output types:
  ╭───┬─────────┬────────╮
  │ # │  input  │ output │
  ├───┼─────────┼────────┤
  │ 0 │ nothing │ string │
  ╰───┴─────────┴────────╯
```

~~End of post~~, below I'll pick a few subcommands I find interesting.

## Spawn Background Jobs🫃🏻

**Spawn** <button class="physical-btn" onclick="new Audio('https://cn.bing.com//dict/mediamp3?blob=audio%2Ftom%2F11%2Fef%2F11EFB3AC01ED4D120081B49EC6A3C959.mp3').play()"> [spɔːn]🔊 </button> means to hatch; in Nushell it means **starting a background job**.

* P.S. I first ran into this word while modding Cyberpunk, where spawning NPCs uses exactly this word (😡why aren't this NPC's clothes loading)

Usage needs little explanation, so straight to an example. `{nu} hexo clean; hexo gen` takes ~10s and blocks the prompt while it runs, so it's more convenient to push it into the background:

```nu
# Start a background job
$ job spawn { hexo clean; hexo gen }

# Confirm it is quietly working in the background
$ job list
╭───┬────┬────────┬──────────────╮
│ # │ id │  type  │     pids     │
├───┼────┼────────┼──────────────┤
│ 0 │ 34 │ thread │ ╭───┬──────╮ │
│   │    │        │ │ 0 │ 5359 │ │
│   │    │        │ ╰───┴──────╯ │
╰───┴────┴────────┴──────────────╯
```

Care about the job's output? I haven't found a corresponding Nushell API yet; one workaround is to redirect stdout/stderr to files:
`{nu} hexo generate o> hexo.log e> hexo.err`

### Process Bound

When you run `{nu} job spawn { ... }`, Nushell actually starts a new **Rust thread** inside the current shell process to run the closure[^1], which means:

- Jobs in different Nushell processes are **independent of each other**; you can't manage them across processes
- When you exit Nushell, background jobs get cleaned up too:

```nu
$ exit
There are still background jobs running (1).
Running `exit` a second time will kill all of them.
```

> [!ERROR] BUG? Orphan Process Warning
>
> Although Nushell tries to clean up jobs, I found that with something like `{nu} job spawn {hexo server}`, which launches **external child processes** (`node`), the cleanup isn't thorough. The node process doesn't die; it becomes an **orphan process**. I filed an issue on GitHub: [job spawn terminates direct child but leaves grandchild processes orphaned · Issue #17378 · nushell/nushell](https://github.com/nushell/nushell/issues/17378)
>
> Suppose:
> - Parent: the Nushell process has PID 53822
> - Child: the bun process started by `{nu} hexo server` has PID 64759
> - Grandchild: the node process started by bun has PID 64762
>
> Run `{nu} ps` before and after exiting Nushell to check their state. You can see that after Nushell exits, the bun process is killed, but the node process's PPID becomes 1 (the init process), meaning it was never cleaned up

### Usage Scenarios

- Uploading files
  - A concrete example: Atuin's Nushell script uses `{nu} job spawn` to create an async upload task [^2]
- Downloading files
- Compile Code etc.

## Freeze 🥶

Just like in a UNIX shell, you can **freeze** a running foreground process:

1. Run `{nu} hx ./test.js` in the foreground
2. Press <kbd>Ctrl+Z</kbd> or send the process a `SIGTSTP` signal; Nushell reports: `{nu} Job 20 is frozen`
3. Do something else...
4. Use `{nu} job unfreeze 20` to bring it back to the foreground and resume

<video autoplay loop muted playsinline>
  <source src="https://assets.vluv.space/job_control.webm" type="video/webm">
</video>

A frozen job no longer consumes CPU, but it still occupies memory and keeps its file handles open, waiting for your next instruction.

|                              | Bash / Zsh       | Nushell                                                           |
| :--------------------------- | :--------------- | :---------------------------------------------------------------- |
| **Resume in foreground**     | `{bash} fg %1`   | `{nu} job unfreeze 20`                                            |
| **Kill the job**             | `{bash} kill %1` | `{nu} job kill 20`                                                |
| **Unfreeze into background** | `{bash} bg %1`   | ❌ [Issue #15196](https://github.com/nushell/nushell/issues/15196) |

[^1]: An anonymous function, often called a lambda function, which accepts parameters and _closes over_ (i.e., uses) variables from outside its scope [Closure | Nushell](https://www.nushell.sh/lang-guide/chapters/types/basic_types/closure.html)
[^2]: [Atuin](https://atuin.sh/) is a popular shell tool that persists shell history commands into a SQLite database and can sync them to a server, giving you cross-shell, cross-device history sync
