/* Copyright 2026 Marimo. All rights reserved. */ import { useAtomValue } from "jotai"; import { ChevronDown, ChevronRight, InfoIcon, NotebookPenIcon, PackageIcon, SquareArrowOutUpRightIcon, TerminalIcon, } from "lucide-react"; import { Fragment, type JSX, useState } from "react"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; import { Button } from "@/components/ui/button"; import { Kbd } from "@/components/ui/kbd"; import { ExternalLink } from "@/components/ui/links"; import { Tooltip } from "@/components/ui/tooltip"; import type { CellId } from "@/core/cells/ids"; import { resolvedMarimoConfigAtom } from "@/core/config/config"; import { renderHTML } from "@/plugins/core/RenderHTML"; import { splitMangledLocals } from "@/utils/local-variables"; import type { MarimoError } from "../../../core/kernel/messages"; import { cn } from "../../../utils/cn"; import { Alert, AlertTitle } from "../../ui/alert"; import { openPackageManager } from "../chrome/panels/packages-utils"; import { useChromeActions } from "../chrome/state"; import { AutoFixButton } from "../errors/auto-fix"; import { MangledSegments } from "../errors/mangled-local-chip"; import { CellLinkError } from "../links/cell-link"; import { processTextForUrls } from "./console/text-rendering"; const CollapsibleTraceback = ({ traceback, }: { traceback: string; }): JSX.Element => { const [isOpen, setIsOpen] = useState(true); return (
{isOpen && (
{renderHTML({ html: traceback })}
)}
); }; const Tip = (props: { title?: string; className?: string; children: React.ReactNode; }): JSX.Element => { return ( {props.title ?? "Tip"} {props.children} ); }; interface Props { cellId: CellId | undefined; errors: MarimoError[]; className?: string; } /** * List of errors due to violations of Marimo semantics. */ export const MarimoErrorOutput = ({ errors, cellId, className, }: Props): JSX.Element => { const chromeActions = useChromeActions(); // The console area (where tracebacks are shown) sits below the cell editor. // When cell outputs are also displayed below the editor, the traceback is // already adjacent, so the "See the console area" hint is redundant. It is // only helpful when outputs are displayed above the editor. const showConsoleHint = useAtomValue(resolvedMarimoConfigAtom).display.cell_output === "above"; let titleContents: string | null = "This cell wasn't run because it has errors"; let alertVariant: "destructive" | "default" = "destructive"; let titleColor = "text-error"; // A small muted overline shown above the title for static errors, where // the cell genuinely never executes. These errors have no separate title: // the body text already describes them, so the overline carries the status. let statusOverline: string | null = null; const liStyle = "my-0.5 ml-8 text-muted-foreground/40"; // Check for certain error types to adjust title and appearance if (errors.some((e) => e.type === "interruption")) { titleContents = "Interrupted"; } else if (errors.some((e) => e.type === "internal")) { titleContents = "An internal error occurred"; } else if (errors.some((e) => e.type === "ancestor-prevented")) { titleContents = "Ancestor prevented from running"; alertVariant = "default"; titleColor = "text-secondary-foreground"; } else if (errors.some((e) => e.type === "ancestor-stopped")) { titleContents = "Ancestor stopped"; alertVariant = "default"; titleColor = "text-secondary-foreground"; } else if (errors.some((e) => e.type === "sql-error")) { titleContents = "SQL error"; } else if ( errors.some( (e) => e.type === "multiple-defs" || e.type === "cycle" || e.type === "setup-refs" || e.type === "import-star", ) ) { // Static errors: the body text describes the problem, so there is no // separate title — only the status overline. titleContents = null; statusOverline = "Cell not run"; } else { // Check for exception type const exceptionError = errors.find((e) => e.type === "exception"); if (exceptionError && "exception_type" in exceptionError) { titleContents = exceptionError.exception_type; } } // Group errors by type const setupErrors = errors.filter( (e): e is Extract => e.type === "setup-refs", ); const cycleErrors = errors.filter( (e): e is Extract => e.type === "cycle", ); const multipleDefsErrors = errors.filter( (e): e is Extract => e.type === "multiple-defs", ); const importStarErrors = errors.filter( (e): e is Extract => e.type === "import-star", ); const interruptionErrors = errors.filter( (e): e is Extract => e.type === "interruption", ); const exceptionErrors = errors.filter( (e): e is Extract => e.type === "exception", ); const strictExceptionErrors = errors.filter( (e): e is Extract => e.type === "strict-exception", ); const internalErrors = errors.filter( (e): e is Extract => e.type === "internal", ); const ancestorPreventedErrors = errors.filter( (e): e is Extract => e.type === "ancestor-prevented", ); const ancestorStoppedErrors = errors.filter( (e): e is Extract => e.type === "ancestor-stopped", ); const syntaxErrors = errors.filter( (e): e is Extract => e.type === "syntax", ); const unknownErrors = errors.filter( (e): e is Extract => e.type === "unknown", ); const sqlErrors = errors.filter( (e): e is Extract => e.type === "sql-error", ); const openScratchpad = () => { chromeActions.openApplication("scratchpad"); }; const renderMessages = () => { const messages: JSX.Element[] = []; if (syntaxErrors.length > 0 || unknownErrors.length > 0) { // Detect if this is a pip/package manager related syntax error const hasPipHint = syntaxErrors.some((error) => error.msg.includes("!pip"), ); // Detect if this is a shell command syntax error (not pip) const hasShellHint = !hasPipHint && syntaxErrors.some((error) => error.msg.includes("use os.subprocess")); messages.push(
{syntaxErrors.map((error, idx) => (
              {processTextForUrls(error.msg, `syntax-${idx}`)}
            
))} {unknownErrors.map((error, idx) => (
              {processTextForUrls(error.msg, `unknown-${idx}`)}
            
))} {hasPipHint && ( )} {hasShellHint && ( )} {cellId && ( )}
, ); } if (setupErrors.length > 0) { messages.push(

The setup cell cannot be run because it has references.

    {setupErrors.flatMap((error, errorIdx) => error.edges_with_vars.map((edge, edgeIdx) => (
  • {": "}{" "} {edge[1].length === 1 ? edge[1][0] : edge[1].join(", ")}
  • )), )}
{cellId && }

The setup cell contains logic that must be run before any other cell runs, including top-level imports used by top-level functions. For this reason, it can't refer to other cells' variables.

Try simplifying the setup cell to only contain only necessary variables.

Learn more at our docs{" "} .

, ); } if (cycleErrors.length > 0) { messages.push(

This cell is in a cycle.

    {cycleErrors.flatMap((error, errorIdx) => error.edges_with_vars.map((edge, edgeIdx) => (
  • {" -> "} {edge[1].length === 1 ? edge[1][0] : edge[1].join(", ")} {" -> "}
  • )), )}
{cellId && }

An example of a cycle is if one cell declares a variable 'a' and reads 'b', and another cell declares 'b' and and reads 'a'. Cycles like this make it impossible for marimo to know how to run your cells, and generally suggest that your code has a bug.

Try merging these cells into a single cell to eliminate the cycle.

Learn more at our docs{" "} .

, ); } if (multipleDefsErrors.length > 0) { const firstName = multipleDefsErrors[0].name; messages.push(

This cell redefines variables from other cells.

{multipleDefsErrors.map((error, idx) => (

{`'${error.name}' was also defined by:`}

    {error.cells.map((cid, cidIdx) => (
  • ))}
))} {cellId && ( )}

marimo requires that each variable is defined in just one cell. This constraint enables reactive and reproducible execution, arbitrary cell reordering, seamless UI elements, execution as a script, and more.

Try merging this cell with the mentioned cells or wrapping it in a function. Alternatively, prefix variables with an underscore (e.g., _{firstName}). to make them private to this cell.

Learn more at our docs{" "} .

to experiment without restrictions on variable names.
, ); } if (importStarErrors.length > 0) { messages.push(
{importStarErrors.map((error, idx) => (

{error.msg}

))} {cellId && ( )}

Star imports are incompatible with marimo's git-friendly file format and reproducible reactive execution.

marimo's Python file format stores code in functions, so notebooks can be imported as regular Python modules without executing all their code. But Python disallows `import *` everywhere except at the top-level of a module.

Star imports would also silently add names to globals, which would be incompatible with reactive execution.

Learn more at our docs{" "} .

, ); } if (interruptionErrors.length > 0) { messages.push(
{interruptionErrors.map((_, idx) => (

{"This cell was interrupted and needs to be re-run."}

))} {cellId && ( )}
, ); } if (exceptionErrors.length > 0) { messages.push(
    {exceptionErrors.map((error, idx) => { if ( error.exception_type === "NameError" && error.msg.startsWith("name 'mo'") ) { return (
  • name 'mo' is not defined.

    The marimo module (imported as{" "} mo) is required for Markdown, SQL, and UI elements.

  • ); } if ( error.exception_type === "NameError" && error.msg.startsWith("name '_") ) { const segments = splitMangledLocals(error.msg); return (
  • Variables prefixed with an underscore are local to a cell{" "} ( docs{" "} ).

    {showConsoleHint && (
    See the console area for a traceback.
    )}
  • ); } // All other exceptions return (
  • {error.raising_cell == null ? (

    {processTextForUrls(error.msg, `exception-${idx}`)}

    {"traceback" in error && error.traceback ? ( ) : ( showConsoleHint && (
    See the console area for a traceback.
    ) )}
    ) : (
    {processTextForUrls(error.msg, `exception-${idx}`)} {"traceback" in error && error.traceback && ( )}
    )}
  • ); })} {exceptionErrors.some((e) => e.raising_cell != null) && ( Fix the error in the mentioned cells, or handle the exceptions with try/except blocks. )} {cellId && }
, ); } if (strictExceptionErrors.length > 0) { messages.push(
    {strictExceptionErrors.map((error, idx) => (
  • {error.blamed_cell == null ? (

    {error.msg}

    ) : (
    {error.msg}
    )}
  • ))} {cellId && ( )} {strictExceptionErrors.some((e) => e.blamed_cell != null) ? "Ensure that the referenced cells define the required variables, or turn off strict execution." : "Something is wrong with your declarations. Fix any discrepancies, or turn off strict execution."}
, ); } if (internalErrors.length > 0) { messages.push(
{internalErrors.map((error, idx) => (

{error.msg}

))} {cellId && }
, ); } if (ancestorPreventedErrors.length > 0) { messages.push(
{ancestorPreventedErrors.map((error, idx) => (
{error.msg} {error.blamed_cell == null ? ( () ) : ( (  blames  ) )}
))} {cellId && ( )}
, ); } if (ancestorStoppedErrors.length > 0) { messages.push(
{ancestorStoppedErrors.map((error, idx) => (
{error.msg}
))} {cellId && ( )}
, ); } if (sqlErrors.length > 0) { messages.push(
{sqlErrors.map((error, idx) => { return (

{error.msg}

); })} {cellId && ( )}
, ); } return messages; }; const title = (
{statusOverline && (
{statusOverline}
)} {titleContents && ( {titleContents} )}
); return ( {title}
{renderMessages()}
); };