nushell/nushell · critical

internal error: missing block

Error message

internal error: missing block

What it means

Panic inside nushell's StateWorkingSet::get_block: a BlockId was passed that is >= the permanent block count but does not index into the working set's delta blocks. Block IDs are assigned as permanent blocks first, then delta blocks appended during the current compile session; a lookup that falls off both ranges means the ID is stale or from a different working set. This is an engine-internal invariant ('internal error') and always indicates a bug in nushell or a misused working set, not bad user input.

Source

Thrown at crates/nu-protocol/src/engine/state_working_set.rs:845

                decl_id,
                name.to_vec(),
                Some(command.description().to_string()),
                command.command_type(),
            ));
        });

        output
    }

    pub fn get_block(&self, block_id: BlockId) -> &Arc<Block> {
        let num_permanent_blocks = self.permanent_state.num_blocks();
        if block_id.get() < num_permanent_blocks {
            self.permanent_state.get_block(block_id)
        } else {
            self.delta
                .blocks
                .get(block_id.get() - num_permanent_blocks)
                .expect("internal error: missing block")
        }
    }

    pub fn get_module(&self, module_id: ModuleId) -> &Module {
        let num_permanent_modules = self.permanent_state.num_modules();
        if module_id.get() < num_permanent_modules {
            self.permanent_state.get_module(module_id)
        } else {
            self.delta
                .modules
                .get(module_id.get() - num_permanent_modules)
                .expect("internal error: missing module")
        }
    }

    pub fn get_block_mut(&mut self, block_id: BlockId) -> &mut Block {
        let num_permanent_blocks = self.permanent_state.num_blocks();
        if block_id.get() < num_permanent_blocks {

View on GitHub (pinned to 2af17cd99e)

Solutions

  1. If you hit this as a user, update nushell and file the reproducing script — the message literally says 'internal error', user code cannot trigger it through valid input
  2. If embedding nushell, always re-resolve BlockIds in the same StateWorkingSet that created them; never reuse IDs after EngineState::merge_delta
  3. Check bounds before lookup: block_id.get() < permanent_state.num_blocks() + delta.blocks.len()
  4. As a maintainer, replace expect with a proper ShellError (or debug_assert + fallback) so the engine reports the bad ID instead of panicking

Example fix

// before (parser/embedder code holding a stale id)
let block = working_set.get_block(old_block_id);

// after: re-resolve the block in the working set that owns it
let block_id = working_set.add_block(block);
let block = working_set.get_block(block_id);
Defensive patterns

Strategy: validation

Validate before calling

fn block_in_range(ws: &StateWorkingSet, id: BlockId) -> bool {
    let n = ws.permanent_state.num_blocks() + ws.delta.blocks.len();
    id.get() < n
}

Type guard

fn owns_block_id(ws: &StateWorkingSet, id: BlockId) -> bool {
    id.get() >= ws.permanent_state.num_blocks()
        && id.get() - ws.permanent_state.num_blocks() < ws.delta.blocks.len()
        || id.get() < ws.permanent_state.num_blocks()
}

Prevention

When it happens

Trigger: Calling get_block with a BlockId captured before a merge_delta/reset (IDs shift when the delta is merged and the delta list empties), or using an ID obtained from a different EngineState/StateWorkingSet instance; also any parser code path that computes a BlockId without going through add_block.

Common situations: Contributing parser/evaluator changes to nushell that cache DeclId/BlockId across working-set lifetimes, plugins or embedders that hold engine IDs while the engine state advances, or a refactor that reorders block registration during compile.

Related errors


AI-assisted analysis of nushell/nushell@2af17cd99e (2026-08-17). Data as JSON: /api/errors/397f34e7d32c4c0e. Report an issue: GitHub.