/* Copyright 2026 Marimo. All rights reserved. */ import { NotebookPenIcon, PackageIcon, SquareArrowOutUpRightIcon, TerminalIcon, } from "lucide-react"; import { Fragment, type JSX } 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 type { CellId } from "@/core/cells/ids"; 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 { CellLinkError } from "../links/cell-link"; import { processTextForUrls } from "./console/text-rendering"; import { renderHTML } from "@/plugins/core/RenderHTML"; 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(); let titleContents = "This cell wasn't run because it has errors"; let alertVariant: "destructive" | "default" = "destructive"; let titleColor = "text-error"; 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-muted-foreground"; 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 { // 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 '_") ) { return (
  • {error.msg}

    Variables prefixed with an underscore are local to a cell{" "} ( docs{" "} ).

    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 ? (
    {renderHTML({ html: error.traceback })}
    ) : (
    See the console area for a traceback.
    )}
    ) : (
    {processTextForUrls(error.msg, `exception-${idx}`)} {"traceback" in error && error.traceback && (
    {renderHTML({ html: 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 = ( {titleContents} ); return ( {title}
{renderMessages()}
); };