feat: 2.1.0 — ExecutorErrorCode taxonomy + structured error events (Phase 1)#209
Merged
Conversation
…hase 1)
Adds stable, fine-grained error codes to every executor exception so
hosts can group errors for logging / Sentry / i18n / telemetry
without parsing message strings. Backward compatible: legacy call
sites of the form ``raise APIError("...", category=...)`` keep
working unchanged — the base class derives a sensible code from
the category via ``ExecutorErrorCode.from_category``.
== What's new ==
1. ``ExecutorErrorCode`` (``core/errors.py``) — a string enum with
~30 codes in ``exec.<component>.<reason>`` format spanning
the api/cli/pipeline/stage/tool/mutation/mcp components. Naming
matches the existing ``ToolErrorCode`` precedent.
2. ``GenyExecutorError.code`` — every executor exception now exposes
a ``code`` attribute resolved as:
explicit kwarg (`code=`) > category-derived (`APIError`) > subclass `_DEFAULT_CODE`
``code`` is always set on the exception instance; downstream
consumers can rely on it never being ``None``.
3. Structured error event payloads. ``pipeline.error`` / ``stage.error``
/ ``api.retry`` now carry:
{
"error": "<stringified message>", // legacy field, kept
"code": "exec.cli.auth_failed", // new, stable
"exception_type": "geny_executor.core.errors.APIError" // new
}
Hosts can switch over to ``data["code"]`` without disturbing
existing consumers reading ``data["error"]``.
4. Explicit code annotations on the most operationally important
raise sites in Stage 6: ``EXEC_API_NO_CLIENT``,
``EXEC_API_RETRY_EXHAUSTED``, ``EXEC_API_STREAM_INCOMPLETE``.
All other Stage 6 APIError raises auto-resolve via the category
default mapping.
5. ``docs/error_codes.md`` — authoritative reference for every code:
recoverability, source raise sites, description, recommended
user-facing action. Includes a "how to add a new code" /
"how to deprecate a code" workflow so the taxonomy stays
curated rather than sprawling.
6. ``tests/contract/test_error_codes_stability.py`` — pins every
shipped code's exact string value in a frozen dict. Renaming /
repurposing / accidentally deleting a code now fails CI before
release. Verifies:
- frozen string values match enum (catches renames)
- every enum member appears in the frozen pin (catches
additions that didn't go through the docs flow)
- all codes match the ``exec.<component>.<reason>`` format
- every ``ErrorCategory`` has a non-fallback default code
- ``APIError`` resolution matrix (explicit > category > default)
== Stability contract ==
Once published in a release, a code's string value never changes.
Renaming or repurposing is a major-version change. The regression
test enforces this.
== Backward compatibility ==
- All existing ``raise APIError("...", category=...)`` call sites
work unchanged and now carry a sensible ``.code`` for free.
- All existing event consumers reading ``data["error"]`` work
unchanged; ``data["code"]`` is purely additive.
- No exception class signatures changed beyond accepting an
optional ``code=`` kwarg.
3138 passed, 8 skipped, 0 failed (+11 new stability tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds stable, fine-grained error codes to every executor exception so hosts can group errors for logging / Sentry / i18n / telemetry without parsing message strings. Fully backward compatible — every existing call site keeps working unchanged.
Why
The current error surface gives downstream consumers only:
APIError,StageError, …) — too coarse for i18n.ErrorCategoryonAPIErroronly — answers "should I retry?" but not "what specifically broke?".Hosts working around this have to grep message text (Geny's frontend currently does exactly this for the red "Error: Claude Code CLI is not authenticated…" banner — verbatim English server message, no i18n hook).
What's new
1.
ExecutorErrorCodeenum (core/errors.py)~30 codes in
exec.<component>.<reason>format spanning the api / cli / pipeline / stage / tool / mutation / mcp components. Naming mirrors the existingToolErrorCodeprecedent. Examples:Full table + recoverability + recommended user-facing action lives in docs/error_codes.md.
2.
GenyExecutorError.codeattributeEvery executor exception now exposes a
codeattribute resolved as:codeis always set on the exception instance; downstream consumers can rely on it never beingNone.3. Structured error event payloads
pipeline.error/stage.error/api.retryevents now carry:{ "error": "<stringified message>", # legacy field, preserved "code": "exec.cli.auth_failed", # new, stable identifier "exception_type": "geny_executor.core.errors.APIError" # new }Hosts can switch over to
data["code"]for i18n / telemetry without disturbing existing consumers that readdata["error"].4. Explicit codes on Stage 6's most operational raise sites
EXEC_API_NO_CLIENT—state.llm_client is Noneconfig bugEXEC_API_RETRY_EXHAUSTED— recoverable category butmax_retrieshitEXEC_API_STREAM_INCOMPLETE— stream ended withoutmessage_completeAll other Stage 6
APIErrorraises auto-resolve via the category default mapping — no per-site change needed.5.
docs/error_codes.mdAuthoritative reference for every code: recoverability, source raise sites, description, recommended user-facing action. Includes "how to add a new code" + "how to deprecate a code" workflow so the taxonomy stays curated.
6.
tests/contract/test_error_codes_stability.pyPins every shipped code's exact string value in a frozen dict. Renaming / repurposing / accidentally deleting a code now fails CI before release. Verifies:
exec.<component>.<reason>format (lowercase, dot-separated, ≤4 segments)ErrorCategoryhas a non-fallback default code (so legacy raises never degrade toexec.unknown)APIErrorresolution matrix (explicit > category > default) works correctly across all combinationsStability contract
Once a release ships, a code's string value never changes. Renaming or repurposing is a major-version change. The regression test enforces this.
Backward compatibility
raise APIError("...", category=ErrorCategory.CLI_AUTH_FAILED)keeps working and now carries.code = ExecutorErrorCode.EXEC_CLI_AUTH_FAILEDfor free.data["code"]is purely additive.code=kwarg added.Roadmap (post-merge)
code, session_logger metadata, WebSocket payload, frontend ErrorBanner renders via i18n keyexecutor.{code}.RuntimeError/ValueErrorto typed exceptions.Test results
3138 passed, 8 skipped, 0 failed (+11 new stability tests; previous baseline was 3127).
🤖 Generated with Claude Code