Stack Trace

開発者が読むようにクラッシュを表示します。自分のコードを先に、フレームワークのフレームは折りたたみ、失敗した行を前後の文脈と一緒に、原因を順番に表示します。Node、Chrome、Firefox、Safari、Bun、Deno、Python のスタックを解析し、コピー、エディターで開く、修正を依頼する操作に対応します。

エージェントのコマンド、ツール呼び出し、テストが失敗すると、エラーの肝心な部分はフレームワーク内部の50行に埋もれてしまいます。Stack Traceはエラー型とメッセージを先頭に置き、自分のフレームをファイル、行、列とともにはっきり表示し、続くフレームワークのフレームを「8 framework frames · next」のような控えめな1行に折りたたみます。

parseStackTraceは、Node、Chrome、Bun、DenoのV8スタック、FirefoxとSafariのスタック、Vitestのフレームとその下のコード、Pythonのトレースバックを読み取ります。[cause]、Caused by:、Pythonの「direct cause」と「during handling」のセクションによる連鎖したcauseにも対応します。node_modules、node:internal、Next.jsの内部、site-packages、標準ライブラリはアプリ外のコードとして分類し、file://やwebpack-internalのパスを読みやすいプロジェクトのパスに変換します。認識できない行も捨てずに残します。

コードが失敗したフレームでは、Code Blockを使って失敗した行をハイライトしたソースを表示できます。長いメッセージはShow moreで省略され、Copyは元のテキストを返し、フレームはgetFrameHrefかonFrameClickでエディタに開けます。コンパクト版は、失敗したツール呼び出し向けの「Failed: TypeError: …」という1行で、開くと同じフレームが表示されます。

  1. HextaUI Pro を入手する

    一度の支払いで、今後追加されるものも含めてすべてのブロックが使えます。料金を見る。

  2. Proレジストリをcomponents.jsonに追加する

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  3. トークンを追加する

    アカウントページでトークンを作成し、.env.local に HEXTAUI_PRO_TOKEN として設定してください。

  4. ブロックを追加する

    pnpm dlx shadcn@latest add @hextaui-pro/stack-trace

ログから

ターミナル、ログ、ツールの結果から生のテキストを渡します。getEditorHrefはフレームをvscode://やcursor://のリンクに変え、Ask to fixは解析済みのエラーを渡すので、エージェントに送り返せます。

"use client"

import { getEditorHref } from "@/components/blocks/stack-trace/parse-stack-trace"
import { StackTrace } from "@/components/blocks/stack-trace/stack-trace"

export function CrashReport({
  log,
  onAsk,
}: {
  log: string
  onAsk: (prompt: string) => void
}) {
  return (
    <StackTrace
      trace={log}
      getFrameHref={(frame) =>
        frame.kind === "app"
          ? getEditorHref(frame, { editor: "cursor", root: "/Users/me/acme" })
          : undefined
      }
      onAskToFix={(error) =>
        onAsk(`Fix this ${error.type}: ${error.message}\n\n${error.raw}`)
      }
    />
  )
}

AI SDKと使う

例外を投げたツールは、errorTextを持つoutput-error状態で終わります。失敗したToolCallの中にコンパクトなStackTraceとして表示すれば、誰かが開くまでそのステップは1行のままです。

"use client"

import { useChat } from "@ai-sdk/react"
import {
  getToolName,
  isToolUIPart,
  type DynamicToolUIPart,
  type ToolUIPart,
  type UIMessage,
} from "ai"

import type { ToolCallProps } from "../tool-calls/tool-call"
import { ToolCalls } from "../tool-calls/tool-group"
import { getToolPartStatus } from "../tool-calls/tool-part"
import { getEditorHref } from "@/components/blocks/stack-trace/parse-stack-trace"
import { StackTrace } from "@/components/blocks/stack-trace/stack-trace"

type ToolPart = ToolUIPart | DynamicToolUIPart

function toCall(
  part: ToolPart,
  streaming: boolean,
  onAsk: (text: string) => void
): ToolCallProps {
  const input = (part.input ?? {}) as { command?: string }
  const failed = part.state === "output-error"
  return {
    id: part.toolCallId,
    kind: getToolName(part) === "runCommand" ? "run" : "other",
    name: getToolName(part),
    subject: input.command,
    status: getToolPartStatus(part, { stopped: !streaming }),
    defaultOpen: failed ? true : undefined,
    content: failed ? (
      <StackTrace
        variant="compact"
        trace={part.errorText}
        getFrameHref={(frame) =>
          getEditorHref(frame, { root: process.env.NEXT_PUBLIC_PROJECT_ROOT })
        }
        onAskToFix={(error) =>
          onAsk(
            `The ${getToolName(part)} call failed with ${error.type}: ${error.message}. Fix it.`
          )
        }
      />
    ) : undefined,
  }
}

function AssistantMessage({
  message,
  streaming,
  onAsk,
}: {
  message: UIMessage
  streaming: boolean
  onAsk: (text: string) => void
}) {
  const calls = message.parts
    .filter(isToolUIPart)
    .map((part) => toCall(part, streaming, onAsk))
  return calls.length ? <ToolCalls calls={calls} /> : null
}

export function Chat() {
  const { messages, status, sendMessage } = useChat()
  const busy = status === "submitted" || status === "streaming"

  return messages.map((message, index) =>
    message.role === "assistant" ? (
      <AssistantMessage
        key={message.id}
        message={message}
        streaming={busy && index === messages.length - 1}
        onAsk={(text) => sendMessage({ text })}
      />
    ) : null
  )
}

Next.jsのエラーバウンダリで

parseErrorはErrorオブジェクトを、名前、メッセージ、causeのチェーンまで含めて読み取ります。開発中はトレースを、本番ではシンプルなメッセージを表示します。

"use client"

import { Button } from "@/components/ui/button"

import { parseError } from "@/components/blocks/stack-trace/parse-stack-trace"
import { StackTrace } from "@/components/blocks/stack-trace/stack-trace"

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <main className="mx-auto flex w-full max-w-2xl flex-col gap-4 p-6">
      {process.env.NODE_ENV === "development" ? (
        <StackTrace label="Crashed" trace={parseError(error)} />
      ) : (
        <p className="text-sm text-muted-foreground">Something went wrong.</p>
      )}
      <Button variant="outline" className="self-start" onClick={reset}>
        Try again
      </Button>
    </main>
  )
}

フレームだけ

parseStackTraceとStackFramesは単体でも使えます。たとえば失敗した各テストの下に短いスタックを表示できます。

"use client"

import { parseStackTrace } from "@/components/blocks/stack-trace/parse-stack-trace"
import { StackFrames } from "@/components/blocks/stack-trace/stack-frames"

export function FailedTest({ name, stack }: { name: string; stack: string }) {
  const error = parseStackTrace(stack, { root: "/Users/me/acme" })

  return (
    <div className="flex flex-col gap-1">
      <p className="text-sm">
        {name}: <span className="text-muted-foreground">{error.message}</span>
      </p>
      <StackFrames frames={error.frames} showSource={false} />
    </div>
  )
}

構造

外側から内側へ組み合わせるパーツ。

パーツ説明
StackTraceエラー本体:型、メッセージ、アクション、フレーム、cause。デフォルトはカード、compactは展開できる1行です。
StackFramesフレームのリストのみ。フレームワークのフレームの折りたたみとソースの抜粋を含みます。
parseStackTrace, parseErrorテキストやErrorオブジェクトをParsedStackTraceに変換する純粋なパーサー。
getEditorHrefフレームのvscode://、cursor://、windsurf://、zed://リンクを作ります。

StackTrace

children以外のすべてのdivプロパティも受け付けます。

プロパティ型デフォルト
trace生のスタックのテキスト、または自分で解析して情報を加えたトレース。
string | ParsedStackTrace–
variantcompactは、開くとフレームが表示される「Failed: …」の1行を描画します。ToolCallの中で使います。
"default" | "compact""default"
labelエラー型の前に置く状態を表す語。「Crashed」や「Test failed」など。
string"Failed"
root絶対パスから取り除くプロジェクトのルート。省略するとnode_modulesや.venvから推定します。
string–
getFrameHrefフレームをリンクにします。たとえばgetEditorHrefを使います。
(frame: StackFrame) => string | undefined–
onFrameClickフレームがクリックされたときに呼ばれます。リンクを自分で処理するにはevent.preventDefault()を呼びます。
(frame: StackFrame, event: MouseEvent) => void–
onAskToFixAsk to fixを表示し、解析済みのエラーを受け取ります。
(trace: ParsedStackTrace) => void–
askLabelAsk to fixボタンのラベル。
string"Ask to fix"
showSourceフレームの下にソースの行を、最上位のフレームには抜粋を表示します。
booleantrue
foldフレームワークまたは内部のフレームが2つ以上続く部分を折りたたみます。
booleantrue
messageLinesShow moreの前に表示するメッセージの行数。
number3
openコンパクト版の制御された開閉状態。
boolean–
defaultOpenコンパクト版を開いた状態で始めるかどうか。
booleanfalse
onOpenChangeコンパクト版が開閉したときに呼ばれます。
(open: boolean) => void–

StackFrames

children以外のすべてのolプロパティも受け付けます。

プロパティ型デフォルト
framesparseStackTraceのフレーム。最も内側が先頭です。
StackFrame[]–
foldアプリ外のフレームが続く部分を折りたたみます。
booleantrue
showSourceソースの行と、最上位フレームの抜粋を表示します。
booleantrue
getFrameHrefフレームをリンクにします。
(frame: StackFrame) => string | undefined–
onFrameClickフレームがクリックされたときに呼ばれます。
(frame: StackFrame, event: MouseEvent) => void–

parseStackTrace(text, options)

ParsedStackTraceを返します。parseError(error, options)はErrorオブジェクトとそのcauseに対して同じことをします。

プロパティ型デフォルト
textスタック、トレースバック、またはそれを含む生のログ出力。
string–
options.root絶対パスから取り除くプロジェクトのルート。
string–
options.classifyフレームの種類を上書きします。たとえばvendoredフォルダをフレームワークのコードとして扱えます。
(frame: StackFrame) => StackFrameKind | undefined–
プロパティ型デフォルト
typeTypeErrorやbilling.plans.PlanNotFoundなどのエラー型。
string–
messageメッセージ。複数行にわたることがあります。
string–
framesどの言語でも最も内側が先頭なので、frames[0]が例外の発生場所です。
StackFrame[]–
raw元のテキスト。Copyが使います。
string–
language解析した形式。
"javascript" | "python"–
codeERR_MODULE_NOT_FOUNDやECONNREFUSEDなどのエラーコード。
string–
detailserrnoやsyscallなど、Nodeがフレームの後に出力する追加のプロパティ。
string–
preambleエラーの前に出力されたログ行。
string–
causeチェーン内の次のエラー。relationはcauseまたはcontextです。
ParsedStackTrace–
プロパティ型デフォルト
kind自分のコード、依存パッケージ、またはランタイム自体。
"app" | "framework" | "internal"–
fn関数名。無名関数ではundefinedで、場所だけを表示します。
string–
fileapp/api/users/route.tsのような、プロジェクトからの読みやすい相対パス。
string–
path報告されたとおりの場所から、file://とwebpackの接頭辞を取り除いたもの。スタックに絶対パスがあれば絶対パスです。
string–
line, columnファイル内の位置。
number–
packageアプリ外のフレームが属するパッケージ。nextやfastapiなど。
string–
source, sourceStartソースの行と、その最初の行番号。複数行あると抜粋として表示されます。
string, number–
raw元の行。パーサーが認識できない行はrawだけを持ちます。
string–

getEditorHref(frame, options)

ディスク上のファイルがないフレームではundefinedを返します。

プロパティ型デフォルト
options.editor使用するURLスキーム。
"vscode" | "cursor" | "windsurf" | "zed""vscode"
options.rootwebpack-internalフレームなどの相対パスと結合されます。
string–
キーアクション
Tabアクション、Show more、フレームのリンク、折りたたまれたフレームの行を移動します。
EnterSpace折りたたまれたフレーム、またはコンパクト版の行を開閉します。
Enterフレームのリンクをエディタで開きます。
  • フレームは番号付きリストなので、スクリーンリーダーは件数とそれぞれの位置を読み上げます。
  • 折りたたまれた行はaria-expanded付きのボタンです。折りたたまれたフレームもブラウザのページ内検索で見つけられ、見つかると折りたたみが開きます。
  • デフォルト表示は状態とエラー型で名前付けされたグループで、各causeは「Caused by …」という名前のセクションです。
  • 状態を色だけで示すことはありません。失敗はアイコンの横にテキストで書かれ、Copyは「Copied」と読み上げます。

使用技術

Stack Trace を構成する無料のHextaUIコンポーネントです。それぞれ単独でインストールできます。

コード

4 個のファイルを components/blocks/stack-trace に追加しました。