Skip to content

feat: self-report a classified exiting{reason} on any startup/load failure - #165

Merged
solderzzc merged 1 commit into
mainfrom
feat/exit-reason-self-report
Sep 5, 2026
Merged

feat: self-report a classified exiting{reason} on any startup/load failure#165
solderzzc merged 1 commit into
mainfrom
feat/exit-reason-self-report

Conversation

@solderzzc

Copy link
Copy Markdown
Member

Summary

  • run() now wraps its entire startup/load path (architecture probe, main model load, --draft-model load, --mtp-assistant-model load, chat-template probe, HTTP port bind) in one top-level do/catch instead of leaving those nine throw sites unhandled.
  • On any thrown error, emits {"event":"exiting","reason":<snake_case>,"detail":<short message>} via the existing emitEvent, then rethrows — process exit status is unchanged.
  • New classifyExitReason(_:phase:) maps the error + a LoadPhase tracked through the load path to one of the daemon's closed reason values (daemon/spec/engine-protocol.md in Aegis-AI):
    • port_conflictIOError with errnoCode == EADDRINUSE while binding.
    • model_load_failed — any failure while in architectureProbe / mainModelLoad / draftModelLoad / mtpAssistantLoad / chatTemplateProbe, or any MalformedChatTemplate regardless of phase.
    • out_of_memory — allocation failures identifiable from the error's message text (most MLX/Metal OOM instead calls fatalError before reaching this path — out of scope, noted in the doc).
    • binary_error — catch-all for anything else.
  • The existing reason:"requested" SIGTERM/SIGINT path is untouched.
  • docs/AEGIS_INTEGRATION.md documents the full reason set and the new detail field.

Test plan

  • swift build -c release — clean build, no new warnings from this change.
  • swift test --filter ExitReasonClassificationTests — 6/6 passing, new unit coverage for classifyExitReason.
  • Manual: nonexistent --model path → {"event":"exiting","reason":"model_load_failed","detail":"fileNotFound(\"main\")"}.
  • Manual: --port already bound (IPv4 127.0.0.1) → {"event":"exiting","reason":"port_conflict","detail":"bind(descriptor:ptr:bytes:): Address already in use) (errno: 48)"}.
  • Confirmed the pre-existing reason:"requested" SIGTERM/SIGINT path is unchanged.

🤖 Generated with Claude Code

…ilure

run() now wraps its whole body (model/architecture/draft/MTP-assistant
load, chat-template probe, port bind) in one top-level catch instead of
per-site handling. classifyExitReason(_:phase:) maps the thrown error
plus a LoadPhase tracked across the load path to one of the daemon's
closed reason values: port_conflict (EADDRINUSE on bind), model_load_failed
(any load-phase failure, including MalformedChatTemplate), out_of_memory
(identifiable allocation failures — most MLX/Metal OOM instead fatalErrors
before reaching this path), and binary_error as the catch-all. The
existing reason:"requested" SIGTERM/SIGINT path is untouched. Every event
now also carries a short `detail` string. Verified manually against a
nonexistent model path (model_load_failed) and an already-bound port
(port_conflict); docs/AEGIS_INTEGRATION.md documents the reason set.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@solderzzc
solderzzc merged commit 6878a9d into main Sep 5, 2026
14 checks passed
@solderzzc
solderzzc deleted the feat/exit-reason-self-report branch September 5, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant