Reading Validation Errors

When a block or stack definition is wrong, Stackie’s job is to tell you exactly what’s wrong, where it is, and how to fix it. The same diagnostic pipeline drives every surface — CLI, MCP, and dashboard — so the answer to “what broke?” is consistent no matter where you’re looking.

The anatomy of a diagnostic

Every validation error Stackie emits has the same fields:

  • code — a stable identifier like E001, useful for searching docs and filing issues
  • severity"error" or "warning"
  • message — one sentence explaining what’s wrong
  • source_file — workspace-relative path of the offending file (Stackie never emits absolute host paths over the wire)
  • span{ start_line, start_col, end_line, end_col }, all 1-indexed
  • hint — short remediation advice when Stackie can suggest one
  • yaml_excerpt — up to ±5 lines of YAML around the span, ready to render

These fields render differently per surface but always carry the same payload.

CLI

Run the linter directly:

stackie blocks lint ~/.stackie/sources/

A failing block produces output like:

error[block.missing-command]: block must define `command`
  --> ~/.stackie/sources/local/echo.yml:1:1
   |
 1 | echo:
   | ^^^^ block defined here
 2 |   description: "Echoes a string"
   |
   = help: add `command: "<the command to run>"` under the block name

The carat underline points to the source span. The help: line is a suggested fix — copy-pasteable when possible.

If you ran the linter against a directory, every failing file gets its own diagnostic block, followed by a summary:

2 errors in 1 file (5 files checked)

Exit code is 0 on success and 1 on any error, so the command works in pre-commit hooks and CI gates without wrapping.

Dashboard

The daemon validates blocks and stacks as you start them, and any failure is recorded as a notification. The dashboard’s top bar shows an unread count; the system-tray menu mirrors the same number in its “View Dashboard” entry, so you’ll see View Dashboard (🔔 1) when something needs your attention even before you open the UI.

On macOS the tray label includes the bell glyph — View Dashboard (🔔 1). On Linux and Windows the bell is dropped because emoji rendering in tray menus is inconsistent across desktop environments, and the label shows View Dashboard (1) instead. The number is the same on every platform.

Clicking a notification deep-links into the Definition tab of the affected stack or block. The Definition tab shows:

  1. The original YAML — Stackie de-serialises the in-memory model back to YAML so you see the exact file it ran with, not a re-rendered subset.
  2. The diagnostic span — the same excerpt the CLI prints, rendered with syntax highlighting and a red wavy underline on the offending tokens.
  3. The full message and code — clickable to copy.
  4. The fix suggestion — when the diagnostic produces one.

If a stack failed because one of its blocks was malformed, the stack’s notification links you to the block’s Definition tab — the chain of “why did this break?” leads to the actual root cause in one click instead of making you trace it manually.

MCP

Agents and editor integrations call the validate_block MCP tool. It takes a single argument — a path to a file or directory — and returns a JSON envelope:

{
  "status": "invalid",
  "diagnostics": [
    {
      "code": "E001",
      "severity": "error",
      "message": "block must define `command`",
      "source_file": "./sources/local/echo.yml",
      "span": {
        "start_line": 1,
        "start_col": 1,
        "end_line": 1,
        "end_col": 5
      },
      "hint": "add `command: \"<the command to run>\"` under the block name",
      "yaml_excerpt": "echo:\n  description: \"Echoes a string\"\n"
    }
  ]
}

status is "invalid" whenever any diagnostic in the array has severity == "error"; warnings alone leave status as "valid". The diagnostics array is empty when the file passes.

source_file is workspace-relative — Stackie redacts absolute host paths before sending them over MCP, so you’ll never see /Users/you/... or C:\Users\you\... in the response. Directories you pass that sit outside the workspace get the bare filename only.

span, hint, and yaml_excerpt are optional — they’re omitted when Stackie can’t compute them. code, severity, and message are always present.

The field names match the CLI and dashboard payloads exactly — if you’ve seen one, you’ve seen them all.

Notification retention

Notifications are kept for 7 days by default and then purged automatically. Adjust this in ~/.stackie/settings.yml:

notifications:
  retention_days: 30

The daemon reads this value at boot and applies it for its lifetime — edit the file and restart the daemon to change retention. Setting retention_days: 0 does not disable the purge; it makes every notification eligible for deletion on the next purge tick (within an hour), which is effectively “do not retain”. To pause purging, raise the value high (e.g. 36500 for ~100 years) — the schema accepts up to 3650 days.

When to file a bug

If the diagnostic message is wrong, misleading, or points at the wrong span, that’s a bug in Stackie — file it at github.com/stackie-dev/stackie with the offending YAML and the diagnostic output. Including the code: field in your report makes triage instant.