feat: host-neutral defaults, poolside adapter, categories migration#77
Merged
Conversation
ServiceName defaults to 'eyrie' (host-neutral); embedders call SetServiceName before any credential access. Config-dir default renamed from 'hawk' to 'eyrie'. Engine API unchanged.
When the default config dir was renamed from 'hawk' to 'eyrie', an existing provider.json in the old dir was silently ignored — upgrading users lost their provider/model selection, deployments, and routing. On first engine start, if the eyrie-dir provider.json does not yet exist, copy it from <UserConfigDir>/hawk/provider.json. One-time, idempotent, never overwrites newer state.
loadOverrides now reads categories.json from the host-neutral eyrie dir (EYRIE_CONFIG_DIR → HAWK_CONFIG_DIR compat → <userdir>/eyrie) to match GetProviderConfigDir, instead of hardcoding the old hawk dir. The one-time legacy_config migration now also copies categories.json from <userdir>/hawk to <userdir>/eyrie on upgrade, alongside provider.json.
GitNexus docs superseded by Graphify integration (PR #72).
Patel230
added a commit
that referenced
this pull request
Jul 21, 2026
* fix(eyrie): make legacy config migration correct and atomic The migration I added in b2cab57 (PR #77) had three real bugs: 1. Read source + dest directly from os.UserConfigDir(), ignoring EYRIE_CONFIG_DIR / HAWK_CONFIG_DIR. A host using a custom EYRIE_CONFIG_DIR ended up with the legacy file copied to a path eyrie never reads — the migration was effectively a no-op for that user, and silently wrote to an invisible location. 2. Swallowed MkdirAll / WriteFile errors with '_ ='. On a partial write or read-only filesystem the destination provider.json could be left corrupt / empty, and the next start looks like a fresh install. Now slog.Warn on each failure (best-effort, non-fatal). 3. TOCTOU between os.Stat and os.WriteFile. Two engines starting in parallel could both miss the file, both write, and race on contents. Now uses .tmp + os.Rename for an atomic swap, so a concurrent engine cannot observe a half-written file. Source + destination now both derived from config.GetProviderConfigDir(), so the migration honors the same env-var resolution as the rest of eyrie. Added tests covering EYRIE_CONFIG_DIR redirection and the atomic-write + idempotency invariants. * fix(eyrie): return error from GetProviderConfigDir; fix goroutine leak in adaptive rate limiter Two real bugs: 1. GetProviderConfigDir panicked with 'user config directory unavailable' on platforms where os.UserConfigDir fails (sandboxed runtimes, restricted $HOME). It now returns (string, error) so callers can recover; a MustGetProviderConfigDir shim is provided for startup paths that genuinely cannot proceed. Updated all 6 production call sites in runtime/, setup/, engine/ and the 2 test files. 2. AdaptiveRateLimitProvider.StreamChat wrapped the inner stream's events channel in a goroutine that blocked on 'wrappedCh <- evt' forever if the caller's ctx was cancelled. The goroutine never observed ctx.Done, never released the inner stream's body, and the caller waited indefinitely. Now selects on ctx.Done and calls result.Close() to release the inner stream, then drains remaining events so the inner parser can complete cleanly. * fix(eyrie): M1-M6 MEDIUM hardening - retry: return a clear error if a request body is set but GetBody is nil, instead of silently retrying a drained body. - conversation: emit EventError before EventDone when a continuation request fails, so truncated responses aren't indistinguishable from clean completions. - coalesce: replace time.Sleep with a cancellable timer that exits on ctx.Done, so the cleanup goroutine doesn't linger for the full TTL on shutdown. - config: delete the exported-but-unused ValidateBaseURL (only test caller removed). - adaptive_ratelimit: audit confirmed recordTokens + shared-window mutations already take a.mu — no code change; documented lock invariant. * chore(eyrie): centralize magic numbers in client/core/constants.go Retry defaults, transport timeouts and cooldown windows now reference named constants. DefaultRetryConfig uses them. Deferred: full split of the 215-line providerSpecs() literal and the batch.go inline retry (LOW effort, mechanical).
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.
All updates from neutral-host-defaults branch:
Closes #76