zylon-ai/private-gpt · error · ValueError

Block cannot be None

Error message

Block cannot be None

What it means

Second pass of block validation in ChatBody: while cross-checking tool_use/tool_result blocks across messages, any None block inside a list content raises this error. It is the same null-block defect as error 329, caught in the tool-consistency loop (which runs after basic validation and can be the first place a null slips through if the earlier loop shape differs).

Source

Thrown at private_gpt/server/chat/chat_models.py:391

        # Check unique tools
        if self.tools:
            tool_names = [tool.name for tool in self.tools]
            if len(tool_names) != len(set(tool_names)):
                raise ValueError(
                    "Duplicate tool names found in the tools list."
                    f" Provided tools: {self.tools}"
                    f" Unique tool names: {set(tool_names)}"
                )

        # Check tool use and result blocks
        tool_uses_ids: set[str] = set()
        tool_results_ids: set[str] = set()
        for message in self.messages:
            if isinstance(message.content, list):
                for block in message.content:
                    if block is None:
                        raise ValueError("Block cannot be None")
                    elif isinstance(block, ToolUseBlock):
                        if message.role != "assistant":
                            raise ValueError(
                                f"Tool use blocks can only be used in assistant messages: {message}"
                            )
                        if block.id in tool_uses_ids:
                            raise ValueError(f"Duplicate tool use ID found: {block.id}")
                        tool_uses_ids.add(block.id)

                    elif isinstance(block, ToolResultBlock):
                        if block.tool_use_id not in tool_uses_ids:
                            raise ValueError(
                                f"Tool result block references an unknown tool use ID: {block.tool_use_id}"
                            )
                        tool_results_ids.add(block.tool_use_id)

                    elif isinstance(block, TLDRBlock):
                        if message.role != "assistant":

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Sanitize content lists before sending: drop None blocks.
  2. Fix block (de)serialization so every entry is a valid block object or removed.
  3. On load from storage, repair histories: [b for b in content if b is not None].

Example fix

# before
msg.content = stored_blocks  # may contain None

# after
msg.content = [b for b in stored_blocks if b is not None]
Defensive patterns

Strategy: validation

Validate before calling

for m in messages:
    if isinstance(m.get('content'), list):
        m['content'] = [b for b in m['content'] if b is not None]

Type guard

def no_null_blocks(msgs: list[dict]) -> bool:
    return all(
        all(b is not None for b in m['content'])
        for m in msgs if isinstance(m.get('content'), list)
    )

Try / catch

except ValidationError as e:
    if 'Block cannot be None' in str(e):
        strip_null_blocks(messages); retry()
    else:
        raise

Prevention

When it happens

Trigger: content arrays containing null alongside tool blocks, e.g. [null, ToolResultBlock(...)]; deserialized stored conversations with sparse block arrays; client SDKs emitting null for unsupported block types in tool loops.

Common situations: Persistent chat stores that leave null placeholders for deleted blocks; partial JSON round-trips of tool conversations; race conditions appending blocks while serializing.

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/03b8dd826553e82c. Report an issue: GitHub.