| name | log-error |
| description | Protocol for recording agent errors into the project's errors store — when to log, what fields to populate, the resolved command, and when to skip. Use whenever a tool call fails, a hook blocks, a claim fails, or any non-trivial error occurs during puzzle work. |
| version | 1.2.0 |
| last_reviewed | 2026-06-23T00:00:00.000Z |
Error Logging Protocol
When a non-trivial error occurs during puzzle work, log it to the project's errors store using the command resolved from .claude/orchestrate.json (see Project config below).
Project config
This skill resolves its mechanics from .claude/orchestrate.json (full schema:
fruit-agent-orchestrate/references/orchestrate-config.md → "Storage block"). Read these keys:
storage.errors.enabled — if false, error logging is disabled for this project: the skill no-ops. Note it (e.g. "error logging disabled for this project") and skip every step below.
storage.errors.logCommand — the log command. null ⇒ derive pmtools error log '<json>'; an explicit string overrides (lccjs: npm run error:log -- '<json>'). Below, <error-log-cmd> means this resolved command; the JSON row schema is identical regardless of which command runs.
storage.dbPath — the SQLite store for self-audit SELECTs. null ⇒ ~/.pmtools/<repo>/pmtools.db (lccjs: ~/.lccjs/lccjs.db). Below, <db> means this resolved path.
Everything else here — the trigger list, error_type taxonomy, when-to-log/skip rules, and the pre-close self-audit protocol — is project-agnostic and applies as written.
Triggers — log when any of these occur
- A Bash command exits non-zero and the failure affects the work (not just a harmless grep miss)
- A tool call (
Edit, Write, Read, Bash) returns an error result
- A claim command fails (closed issue, already claimed, wrong syntax)
- A git operation fails (
push, rebase, commit)
- A
gh CLI call returns an error (rate limit, not found, auth)
- A SQLite / velocity-log / error-log call fails
- A skill invocation errors or returns unexpected output
- A pre-commit / commit-msg / pre-push hook exits non-zero and blocks the commit
Always log
Log every error, misfire, glitch, and mistake — including those immediately retried and resolved with no lasting impact. A single resolved conflict is noise; ten resolved conflicts in a week is a pattern. Use the notes field to record how it was resolved, not as a reason to skip the row.
Skip when (de-duplication only)
- The error message is a purely informational warning with no work-plan impact (e.g.
[MODULE_TYPELESS_PACKAGE_JSON], deprecation notices) — these are not errors; log nothing
- The same error has already been logged for this ticket in this session
The command
Use the resolved <error-log-cmd> (pmtools: pmtools error log '<json>'; lccjs: npm run error:log -- '<json>'). The JSON row schema below is the same either way:
<error-log-cmd> '{"occurred_iso":"<ISO8601>","agent":"<NAME>","model":"<model>","ticket":<N>,"error_type":"<TYPE>","message":"<raw message>","context":"<JSON>","notes":"<annotation>"}'
Capture occurred_iso with date '+%Y-%m-%dT%H:%M:%S%z' at the moment of failure.
Field guide
| Field | Required | Value |
|---|
occurred_iso | YES | ISO 8601 with tz offset, captured at moment of error |
agent | yes | Terminal/worktree name (e.g. CHERRY) |
model | yes | Canonical short-form: sonnet-4.6, opus-4.8, haiku-4.5 |
ticket | if in puzzle context | Active GitHub issue number (integer) |
error_type | yes | One of the controlled values below |
message | yes | First ~200 chars of the raw error or stderr |
context | recommended | JSON object with type-specific fields (see below) |
notes | optional | Free-form annotation about impact or workaround |
error_type vocabulary
| Code | When to use |
|---|
TOOL_DENIED | User rejected a tool permission prompt (Bash, Edit, Write, etc.) |
HOOK_BLOCK | pre-commit / commit-msg / pre-push hook exited non-zero |
CLAIM_FAIL | the resolved claim command failed (closed issue, already claimed, missing --as) |
BASH_FAIL | Any Bash command exited non-zero with work impact |
GIT_FAIL | git push, git rebase, git commit failed |
GIT_STATE | Git/shell state mismatch: getcwd errors (cwd deleted), "not a working tree", detached HEAD, etc. |
GH_FAIL | gh CLI / GitHub API error (rate limit, network, not found) |
GH_INFO | gh returned a non-error warning that revealed a wrong workflow assumption ("already closed", "already merged") |
DB_FAIL | a velocity-log, error-log, or any SQLite operation failed |
FILE_FAIL | Read / Write / Edit tool failure (path not found, permission denied) |
EDIT_PRECOND | Edit/Write precondition not met: old_string not found, file not read before edit, "no changes to make" |
SKILL_FAIL | Skill invocation errored or produced unexpected output |
NETWORK_FAIL | Timeout or connectivity error on web fetch / API call |
VALIDATION_FAIL | Schema validation error (velocity:log field check, etc.) |
OTHER | Fallback for errors that resist quick categorization. Use when no existing code fits and picking the right one would take more than a few seconds — log promptly with OTHER rather than delaying to find the perfect type. error_type may be corrected retroactively via a DB UPDATE once the right code is known. |
context JSON shapes by type
{"cmd": "git push origin HEAD:main", "exit_code": 1, "stderr": "first ~100 chars"}
{"tool": "Bash", "cmd_preview": "rm -rf /tmp/foo"}
{"hook": "pre-push", "stderr": "first ~100 chars of hook output"}
{"cmd": "npm run claim -- 880 --as CHERRY", "reason": "already claimed by GRAPE"}
{"tool": "Edit", "path": "/src/core/assembler.js", "error": "ENOENT"
Example row
<error-log-cmd> '{
"occurred_iso": "2026-06-05T16:30:00-1000",
"agent": "CHERRY",
"model": "sonnet-4.6",
"ticket": 880,
"error_type": "BASH_FAIL",
"message": "git push: rejected — updates were rejected because the remote contains work",
"context": "{\"cmd\":\"git push origin HEAD:main\",\"exit_code\":1}",
"notes": "resolved via git pull --rebase"
}'
Decision: manual, not hook-triggered
Error logging is a deliberate manual step, not an automated hook. Reasons:
- Not every non-zero exit is an error — harmless informational warnings (e.g.
[MODULE_TYPELESS_PACKAGE_JSON], deprecation notices) are not errors and generate no row.
- The agent has context the hook doesn't: which ticket is active, what the error meant in context, how it was resolved.
- Hook-triggered logging would double-count identical retries that should be de-duplicated within a session.
Log the row at the moment the error occurs, even if you expect to resolve it immediately.
Pre-close self-audit (required — RULES.md 16 / R021)
Logging "at the moment of failure" is the ideal, but it is easy to forget: you self-correct a misfire, move on, and the row never gets written — so the table under-reports (the #1108 repro: 3 errors went unlogged until a human asked, then backfilled as rows 49–51). The backstop, mandated as a close step, is a transcript self-audit — chosen as Option D in #1117 precisely because the close command cannot see the conversation but you can:
Before the velocity log at every close:
- Re-read your session from the point you claimed the ticket to now.
- Enumerate every event matching the triggers above — including resolved ones.
- For each, confirm a row exists or log it now:
sqlite3 <db> "SELECT id,error_type,message FROM errors WHERE ticket=N"
- State the outcome explicitly in the closing comment — one of:
error self-audit: N row(s) logged (#ids …)
error self-audit: no loggable errors this session
The explicit statement is the point: it turns silence into a checkable acknowledgement, so a clean session and a forgotten log stop looking identical. The next-best-action pre-close checklist carries this as a question; #1118 adds a COMPLIANCE_FAIL type so a forgotten-then-caught episode is itself recordable — when the audit catches a miss, log both the original error row(s) and (once #1118 lands) one COMPLIANCE_FAIL row.
Querying logged errors
<db> is the resolved storage.dbPath (default ~/.pmtools/<repo>/pmtools.db; lccjs ~/.lccjs/lccjs.db).
sqlite3 <db> \
"SELECT id, occurred_iso, agent, error_type, message FROM errors ORDER BY id DESC LIMIT 10"
sqlite3 <db> \
"SELECT error_type, COUNT(*) as n FROM errors GROUP BY error_type ORDER BY n DESC"
sqlite3 <db> \
"SELECT id, error_type, message FROM errors WHERE ticket = 880"
sqlite3 <db> \
"SELECT id, occurred_iso, error_type, message FROM errors WHERE agent = 'CHERRY' ORDER BY occurred_iso"
Related
docs/errors-schema.md — canonical field reference and full column rationale
puzzle-velocity skill — velocity row logging (parallel discipline for time tracking)
docs/velocity-schema.md — velocity table schema (models the errors table design)