Skip to content

feat(memory): warn before the embedding budget silently stops memory - #5402

Merged
M3gA-Mind merged 9 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/5324-embedding-budget-warnings
Aug 5, 2026
Merged

feat(memory): warn before the embedding budget silently stops memory#5402
M3gA-Mind merged 9 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/5324-embedding-budget-warnings

Conversation

@YellowSnnowmann

@YellowSnnowmann YellowSnnowmann commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

When the managed embedding budget runs out, every embed job fails as unrecoverable and the Memory Tree silently stops ingesting. The only visible signal was a yellow banner inside a settings panel nobody opens, so the reporting user experienced it as "the app has been broken for a month" (936 unrecoverable failures, sync 32 days stale).

Most of the machinery for this already existed, so this PR reuses rather than rebuilds: the typed budget_exhausted cause, first_blocking_cause on the wire, UserErrorCenter, useUsageState, UpsellBanner, and requeue_failed.

  • Warn early — 75% dismissible / 90% non-dismissible banner, shell-mounted so it reaches the user on any screen. Gated on embeddings actually billing against the managed budget, so BYO-key and local-Ollama users never see a false alarm.
  • Unbury the alert — the typed cause escalates from the settings panel into UserErrorCenter, which survives route changes, plus a once-per-session native OS notification.
  • Name the state — "Paused: embedding budget reached" instead of "Error — 936 unrecoverable failures need action". Derived frontend-side, so the wire payload is unchanged for older clients and the existing status precedence rules are untouched.
  • One-click fix — every CTA deep-links to Connections → Embeddings, where both remediations live. The copy never requires knowing what an embedding is.
  • Un-park dead jobs — changing the embedding provider or adding a key requeues failed jobs at the four provider-change sites, so "unrecoverable" means "needs user action", not "never retried again".
  • Honest healthmemory_tree_pipeline_status reports degraded when the queue holds eligible work but has settled nothing for 6h.

Three decisions worth a reviewer's attention

1. The issue's evidence is misattributed. It cites rebuild added=0 evicted=0 kept=0 as proof the pipeline produces no output, but that log is agent/learning/stability_detector.rs — agent learning, unrelated to the memory pipeline. There is no added=0 signal to key on, so AC-7 is implemented as the honest equivalent below.

2. The stall signal measures idle time, not backlog depth. This distinction is the whole correctness argument, and both naive alternatives misfire on exactly the heavy users this issue is about:

  • Not MIN(created_at_ms) over ready rows — mark_deferred parks a backing-off job by leaving status = 'ready' and pushing available_at_ms forward, so deliberately-asleep work would count as waiting.
  • Not the age of the oldest eligible row either — a re-embed backfill enqueues thousands of rows at once, so six hours into a healthy drain of a 68k-chunk workspace the oldest un-drained row is by definition hours old. That would flag a big, slow, working backfill as broken.

So the measure is time since the queue last settled any job. A pipeline making progress refreshes completed_at_ms continuously regardless of backlog depth. queue_idle_ms_ignores_deep_but_draining_and_deferred_backlogs pins both false-positive shapes.

3. Scope deviations, both deliberate. The retry requeues all failed jobs rather than only budget_exhausted or the last 7 days — scoping either way needs a new filtered query in the vendored tinycortex submodule, and the wider net is bounded (an unrelated failure re-fails once and re-parks, and this only runs on an explicit user config change). And the 75/90% thresholds read the shared managed cycle budget because no separate embedding meter exists — managed embeddings return the identical USER_INSUFFICIENT_CREDITS error as chat.

Out of scope

The email-on-exhaustion criterion cannot be implemented here: src/api/ has no send path and the SDK exposes no route. It is implemented backend-side in tinyhumansai/backend#1212, which triggers at the same managed-embeddings refusal and dedupes on a unique (userId, kind, cycleKey) index so 936 failed jobs cannot become 936 emails. The native OS notification in this PR is the client-side half, for users who do have the app open.

Acceptance criteria

  • In-app banner at 75% and 90% of managed embedding budget
  • Persistent alert on exhaustion, not buried in settings
  • N/A: Email notification on exhaustion — cannot ship from this repo (src/api/ has no send path and the SDK exposes no route). Implemented backend-side in tinyhumansai/backend#1212 (closes backend#1211), deduped to at most one email per user per billing cycle; the once-per-session native OS notification in this PR is the client-side half.
  • Status label distinct from other error states
  • CTA links directly to the embedding configuration screen
  • Failed jobs retried when a new embedding provider is configured
  • memory_tree_pipeline_status returns degraded when the pipeline produces no output

Test plan

  • cargo test --lib memory:: — 1388 passed
  • cargo test --lib config:: — 649 passed
  • cargo test --lib embeddings — 83 passed
  • Vitest across 10 affected suites — 146 passed
  • Full Vitest sweep — 434 files / 5012 passed (run before the final review-fix round; affected suites re-run green after)
  • pnpm typecheck
  • pnpm lint — 0 warnings in changed files
  • pnpm format:check — Prettier + cargo fmt (core and Tauri)
  • pnpm i18n:check / i18n:english:check — 0 missing, 0 extra, 0 unexpected English across 13 locales

New tests cover the threshold boundaries, the managed-provider gate, dismissal not silencing the next escalation, the provider re-read in both directions, the budget label not overriding a manually-paused tree, requeue idempotency, and the two stall false-positive shapes.

Closes #5324

Summary by CodeRabbit

  • New Features

    • Added embedding budget banners for approaching and exhausted usage, with links to embedding settings and session notifications.
    • Added actionable Memory Tree statuses and alerts when embedding capacity is exhausted.
    • Failed memory jobs can resume after relevant embedding provider changes.
    • Added detection and messaging for stalled memory processing.
  • Bug Fixes

    • Improved routing, dismissal behavior, error handling, and recovery guidance for budget issues.
    • Prevented unrelated settings changes from resuming failed jobs.
  • Localization

    • Added translated messaging across supported languages.

When the managed embedding budget ran out, every embed job failed as
`unrecoverable` and the Memory Tree stopped ingesting. The only signal was a
yellow banner inside a settings panel nobody opens, so a user reported this as
"the app has been broken for a month" with 936 unrecoverable failures and a
32-day-stale sync.

Most of the machinery already existed and is reused rather than rebuilt: the
typed `budget_exhausted` cause, `first_blocking_cause` on the wire,
`UserErrorCenter`, `useUsageState`, `UpsellBanner`, and `requeue_failed`.

- Warn at 75% (dismissible) and 90% (not) via a shell-mounted banner, gated on
  embeddings actually billing against the managed budget so BYO-key and local
  users never see a false alarm.
- Escalate the typed cause out of the settings panel into `UserErrorCenter`,
  which survives route changes, plus a once-per-session OS notification.
- Label the state "Paused: embedding budget reached" instead of "Error - 936
  unrecoverable failures", derived frontend-side so the wire payload is
  unchanged for older clients.
- Deep-link every CTA to Connections -> Embeddings, where both remediations
  (local Ollama, own API key) live.
- Un-park failed jobs when the user changes their embedding provider or adds a
  key, at the four provider-change sites. Deliberately not on login, which
  would be a retry storm.
- Report `degraded` when the queue holds eligible work but has not settled any
  job for 6h. This measures idle time, not backlog depth: a deep-but-draining
  backfill and deferred (backing-off) work must not be flagged, or the metric
  misfires on exactly the heavy users this issue is about.

The issue cites `rebuild added=0 evicted=0 kept=0` as evidence, but that log is
`agent/learning/stability_detector.rs` - agent learning, not the memory
pipeline. No `added=0` signal exists, hence the idle-time measure above.

Email on exhaustion cannot live in this repo (`src/api/` has no send path and
the SDK exposes no route); tracked as tinyhumansai/backend#1211.

Closes tinyhumansai#5324
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds managed embedding-budget alerts, memory error routing, provider-change job recovery, effective-provider reporting, and stalled pipeline detection. It also adds localized copy and extensive frontend and backend tests.

Changes

Embedding budget recovery

Layer / File(s) Summary
Budget state and banner
app/src/services/api/embeddingsApi.ts, app/src/hooks/useEmbeddingBudgetState.ts, app/src/components/upsell/*, app/src/App.tsx, app/src/lib/i18n/*
The app derives embedding-budget state, renders warnings in the desktop shell, sends one exhaustion notification per session, and links to embeddings settings.
Memory failure reporting
app/src/types/userError.ts, app/src/lib/userErrors/*, app/src/components/intelligence/*, app/src/components/userErrors/UserErrorCenter.tsx
Memory budget failures become workspace-scoped errors with dedicated status text, global reporting, and an embeddings-settings action.
Provider-change recovery
src/openhuman/memory/queue/*, src/openhuman/config/ops/*, src/openhuman/inference/embeddings/rpc.rs, src/openhuman/memory/tree/score/embed/*, tests/embeddings_rpc_e2e.rs
Embedding-related setting changes requeue failed jobs when applicable, report counts and errors, and expose the effective provider separately from the configured provider.
Stalled pipeline status
src/openhuman/memory/tree/tree/rpc.rs
The pipeline measures eligible queue idle time and marks work as degraded after six hours while preserving higher-priority states.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant EmbeddingSettings
  participant EmbeddingQueue
  participant MemoryPipeline
  participant MemoryTreeStatusPanel
  participant UserErrorCenter
  EmbeddingSettings->>EmbeddingQueue: requeue failed jobs after provider change
  EmbeddingQueue->>MemoryPipeline: restore ready jobs
  MemoryPipeline->>MemoryTreeStatusPanel: report budget or stall status
  MemoryTreeStatusPanel->>UserErrorCenter: report actionable budget failure
Loading

Possibly related issues

  • tinyhumansai/backend#1211: The issue covers client-side budget warnings, notifications, CTAs, and requeue behavior implemented by this PR.

Suggested labels: feature, rust-core, memory, bug

Suggested reviewers: senamakel

Poem

A rabbit watches budgets rise,
Then marks the queue beneath the skies.
When limits pause the memory stream,
Settings show the recovery scheme.
Failed jobs wake when providers change,
And stalled paths become less strange.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: warning users before managed embedding budgets stop Memory Tree ingestion.
Linked Issues check ✅ Passed The PR implements the in-repository objectives in [#5324], including warnings, alerts, status, CTAs, requeueing, and degraded pipeline reporting.
Out of Scope Changes check ✅ Passed The changes support [#5324] through related frontend, backend, localization, compatibility, and regression-test updates; no unrelated scope is evident.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

Comment @coderabbitai help to get the list of available commands.

@YellowSnnowmann
YellowSnnowmann marked this pull request as ready for review August 5, 2026 08:30
@YellowSnnowmann
YellowSnnowmann requested a review from a team August 5, 2026 08:30

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai coderabbitai Bot added bug feature Net-new user-facing capability or product behavior. memory Memory store, memory tree, recall, summarization, and embeddings in src/openhuman/memory/. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Aug 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/components/intelligence/MemoryTreeStatusPanel.tsx (1)

348-385: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the resolved blocking cause for all budget behavior.

Line 348 reads only first_blocking_cause. Line 407 already supports degraded.cause as an older payload shape. When only degraded.cause.code is budget_exhausted, the panel shows neither the budget status label nor the CTA, and it does not report the persistent user error.

Derive one blockingCause before the effect. Use its code for reporting and isBudgetExhausted. Add a regression test for this payload shape.

Proposed fix
-  const blockingCauseCode = status?.first_blocking_cause?.code ?? null;
+  const blockingCause = status?.first_blocking_cause ?? status?.degraded?.cause ?? null;
+  const blockingCauseCode = blockingCause?.code ?? null;
@@
-    status?.first_blocking_cause?.code === 'budget_exhausted' &&
+    blockingCause?.code === 'budget_exhausted' &&
     (statusKind === 'error' || statusKind === 'degraded');
@@
-  const blockingCause = status?.first_blocking_cause ?? status?.degraded?.cause ?? null;

Also applies to: 452-468

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/intelligence/MemoryTreeStatusPanel.tsx` around lines 348 -
385, Resolve a single blockingCause before the useEffect by preferring
status.first_blocking_cause and falling back to the legacy degraded.cause
payload. Use blockingCause.code for reportMemoryPipelineFailure and
isBudgetExhausted so the budget label, CTA, and persistent error reporting work
for both payload shapes; add a regression test covering degraded.cause.code ===
'budget_exhausted'.
🧹 Nitpick comments (1)
app/src/lib/i18n/pt.ts (1)

7284-7293: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Split the locale map to meet the file-size limit.

app/src/lib/i18n/pt.ts contains 7,394 lines. The repository limit for TypeScript files is 500 lines. Move locale groups into focused modules and compose them from the locale entry point.

As per coding guidelines, TypeScript files must stay at or below 500 lines.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/lib/i18n/pt.ts` around lines 7284 - 7293, Split the oversized
Portuguese locale map exported by the locale entry point into focused modules,
keeping each TypeScript file at or below 500 lines. Move complete locale groups
together, import those modules from pt.ts, and compose the final locale object
without changing any translation keys or values.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/hooks/useEmbeddingBudgetState.ts`:
- Around line 105-108: The !hasUsage branch in useEmbeddingBudgetState must
clear the stale provider, set providerLoading to true before the next provider
read, and then return so a new session cannot reuse the previous user’s managed
provider; add a regression test covering sign-out/sign-in from a managed first
session to a local second session.

In `@app/src/lib/i18n/de.ts`:
- Around line 7373-7374: Update the memoryBudget.approachingMessage German
translation to use a space before the percent sign, changing the placeholder
formatting from {pct}% to {pct} % while leaving the surrounding message
unchanged.

In `@app/src/lib/i18n/en.ts`:
- Around line 1213-1215: Split the namespace maps in en.ts into static
translation modules, keeping every *.ts module at or below 500 lines, then
compose those modules into the exported en map. Preserve every existing
translation key and value, including memoryTree.status.statusBudgetExhausted,
without changing the public export shape.

In `@app/src/lib/i18n/fr.ts`:
- Around line 7351-7353: Update the `memoryBudget.approachingTitle` translation
to explicitly refer to the managed embedding budget rather than an embeddings
limit, while leaving the adjacent `memoryBudget.approachingMessage` unchanged.
- Line 6998: Add the missing userErrors.scope.memory translation key to en.ts
and every supported locale file, using the existing scope-label structure and
each locale’s appropriate translation. Ensure UserErrorCenter.tsx can resolve
the dynamically constructed memory scope label without falling back to the raw
scope value.

In `@app/src/lib/i18n/hi.ts`:
- Line 6832: Update the Hindi translation for
memoryTree.status.statusBudgetExhausted to use wording that means “paused”
rather than “stopped,” preserving the recoverable paused-state meaning while
keeping the embedding-budget context.

In `@app/src/lib/i18n/pl.ts`:
- Line 6933: Split the large Polish locale map in pl.ts into chunk modules of no
more than 500 lines, preserving all existing translation keys and values. Keep
pl.ts as a small aggregator that imports and combines the chunks, and place the
section containing memoryTree.status.statusBudgetExhausted in the appropriate
chunk.
- Around line 7273-7275: Add the missing userErrors.scope.workspace translation
key to the locale represented by this file, matching the established scope-label
structure and wording conventions. Ensure the key is present consistently in
every locale’s translation object so UserErrorCenter can resolve
workspace-scoped errors without falling back to the raw scope value.

In `@src/openhuman/config/ops/model.rs`:
- Around line 251-259: Restrict failed-job recovery to embedding remediation: in
src/openhuman/config/ops/model.rs lines 251-259 and 325-332, call
requeue_failed_after_provider_change only when the embedding provider changes;
in src/openhuman/inference/embeddings/rpc.rs lines 370-377, keep recovery
limited to provider remediation while retaining set_api_key for credential
recovery. Add tests confirming unrelated settings saves leave failed jobs
parked.

In `@src/openhuman/memory/queue/ops.rs`:
- Around line 64-84: Update requeue_failed_after_provider_change in ops.rs to
use the required stable domain log format instead of the current [memory::jobs]
messages. Add a debug-level entry log and debug-level outcome logs for each
branch with correlation data for the requeue attempt, and keep the
state-transition and success/failure records in the domain/rpc prefix style
rather than the existing ad hoc wording.

In `@src/openhuman/memory/tree/tree/rpc.rs`:
- Around line 747-752: Update the reference timestamp calculation in the
idle-time function to use the later of last_settled_ms and oldest_eligible_ms,
so newly eligible work starts its idle period when it arrives rather than
inheriting historical idle time. Preserve the existing zero-clamping behavior,
and add a test covering a historical completed job followed by newly eligible
work.

---

Outside diff comments:
In `@app/src/components/intelligence/MemoryTreeStatusPanel.tsx`:
- Around line 348-385: Resolve a single blockingCause before the useEffect by
preferring status.first_blocking_cause and falling back to the legacy
degraded.cause payload. Use blockingCause.code for reportMemoryPipelineFailure
and isBudgetExhausted so the budget label, CTA, and persistent error reporting
work for both payload shapes; add a regression test covering degraded.cause.code
=== 'budget_exhausted'.

---

Nitpick comments:
In `@app/src/lib/i18n/pt.ts`:
- Around line 7284-7293: Split the oversized Portuguese locale map exported by
the locale entry point into focused modules, keeping each TypeScript file at or
below 500 lines. Move complete locale groups together, import those modules from
pt.ts, and compose the final locale object without changing any translation keys
or values.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d2a844e7-1e4c-4939-90d1-c48f43b9e7b5

📥 Commits

Reviewing files that changed from the base of the PR and between d75b0a4 and 0031a5d.

📒 Files selected for processing (32)
  • app/src/App.tsx
  • app/src/__tests__/App.webviewOverlay.test.tsx
  • app/src/components/intelligence/MemoryTreeStatusPanel.test.tsx
  • app/src/components/intelligence/MemoryTreeStatusPanel.tsx
  • app/src/components/upsell/MemoryEmbeddingBudgetBanner.tsx
  • app/src/components/upsell/__tests__/MemoryEmbeddingBudgetBanner.test.tsx
  • app/src/components/userErrors/UserErrorCenter.tsx
  • app/src/hooks/__tests__/useEmbeddingBudgetState.test.ts
  • app/src/hooks/useEmbeddingBudgetState.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/lib/userErrors/__tests__/classify.test.ts
  • app/src/lib/userErrors/classify.ts
  • app/src/lib/userErrors/report.ts
  • app/src/types/userError.ts
  • src/openhuman/config/ops/model.rs
  • src/openhuman/inference/embeddings/rpc.rs
  • src/openhuman/memory/queue/mod.rs
  • src/openhuman/memory/queue/ops.rs
  • src/openhuman/memory/tree/tree/rpc.rs

Comment thread app/src/hooks/useEmbeddingBudgetState.ts Outdated
Comment thread app/src/lib/i18n/de.ts Outdated
Comment thread app/src/lib/i18n/en.ts
Comment thread app/src/lib/i18n/fr.ts
Comment thread app/src/lib/i18n/fr.ts Outdated
Comment thread app/src/lib/i18n/pl.ts
Comment thread app/src/lib/i18n/pl.ts
Comment thread src/openhuman/config/ops/model.rs Outdated
Comment thread src/openhuman/memory/queue/ops.rs Outdated
Comment thread src/openhuman/memory/tree/tree/rpc.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0031a5d18d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/openhuman/memory/tree/tree/rpc.rs Outdated
Comment thread app/src/hooks/useEmbeddingBudgetState.ts Outdated
@YellowSnnowmann
YellowSnnowmann marked this pull request as draft August 5, 2026 08:48
… scope, budget-banner gaps

Review-cycle fixes for the embedding-budget warning PR (tinyhumansai#5324):

- queue_idle_ms: use the later of last_settled_ms and oldest_eligible_ms so
  work enqueued after an idle/empty period starts its own idle window instead
  of inheriting a stale completion (would have flagged degraded immediately).
  CodeRabbit + Codex.
- Requeue-on-provider-change: gate the failed-job un-park to an actual embedder
  change in apply_model_settings / apply_memory_settings /
  embeddings::update_settings; set_api_key stays the unconditional credential
  path. An unrelated settings save no longer restarts terminal jobs. CodeRabbit.
- useEmbeddingBudgetState: gate the read on isAuthenticated, clear stale
  provider on sign-out, and read the managed budget directly when embeddings
  are managed but chat is fully routed away. CodeRabbit + Codex.
- MemoryTreeStatusPanel: derive blockingCause once
  (first_blocking_cause ?? degraded.cause) so a legacy degraded.cause-only
  payload still escalates, labels, and shows the CTA. CodeRabbit.
- i18n: add userErrors.scope.workspace to all 14 locales (the scope the memory
  pipeline failure emits; there is no 'memory' scope); de percent spacing,
  fr approaching-title, hi paused wording.

New tests: idle-time fresh-work regression, requeue-scope gate (model + memory
settings), degraded.cause panel shape, session/routed-away hook cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@YellowSnnowmann

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews — worked through every comment in 58370ac. Summary:

Correctness

  • queue_idle_ms stall detection (CodeRabbit + Codex): now uses the later of last_settled_ms and oldest_eligible_ms, so a job enqueued after an idle period isn't reported as a stall against a stale completion. New regression test.
  • Failed-job requeue over-trigger (CodeRabbit): scoped the un-park to an actual embedder change at all four sites (set_api_key stays the credential path). Tests confirm unrelated saves leave jobs parked.
  • useEmbeddingBudgetState (CodeRabbit + Codex): gate the read on isAuthenticated, clear stale provider on sign-out, and read the managed budget directly when embeddings are managed but chat is routed away — so the 75/90% warning reaches that cohort too.

Outside-diff: MemoryTreeStatusPanel.tsx

Fixed the degraded.cause-only payload shape. blockingCause (first_blocking_cause ?? degraded.cause) is now derived once, before the effect, and drives the escalation, the status label, the CTA, and the banner — so a payload carrying only degraded.cause.code === 'budget_exhausted' no longer renders the banner while silently dropping the label/CTA/escalation. Added a regression test for that shape.

i18n

  • Added userErrors.scope.workspace to en + all 12 locales (the scope the memory-pipeline failure actually emits). The userErrors.scope.memory suggestion is a false positive — there is no 'memory' member in UserErrorScope; the code path is scope: 'workspace'.
  • de percent spacing, fr approaching-title wording, hi paused wording — all applied.

Deliberately not addressed

  • Locale-file size (en.ts / pl.ts / pt.ts > 500 lines): pre-existing (these files were 7k+ lines before this PR, which appends ~12–16 each). Splitting the 13 locale files into ≤500-line modules is a repo-wide refactor for its own PR, not a memory bugfix.

Verification

cargo test (memory / config / embeddings targets incl. new tests), Vitest for the affected suites, pnpm typecheck, eslint (0 warnings on changed files), cargo fmt/prettier, pnpm i18n:check + i18n:english:check (0 missing / 0 extra / 0 unexpected English) — all green.

@YellowSnnowmann
YellowSnnowmann marked this pull request as ready for review August 5, 2026 09:57

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 5, 2026
The escalation dispatch fires from an effect after the pipeline status
resolves, so asserting `mockDispatch` synchronously was timing-dependent —
it passed locally but flaked under CI's slower coverage run
("expected vi.fn() to be called at least once"). Wrap it in `waitFor`,
matching the sibling first_blocking_cause escalation test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/hooks/useEmbeddingBudgetState.ts (1)

131-217: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add privacy-safe lifecycle diagnostics.

Add grep-friendly diagnostics for the authentication branch, provider read start and result, fallback-read decision, and polling state. Record safe categories and error kinds. Do not pass raw err values to diagnostics.

As per coding guidelines, “Add verbose, grep-friendly diagnostics for new or changed flows” and “never log secrets or full PII.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/hooks/useEmbeddingBudgetState.ts` around lines 131 - 217, Add
privacy-safe, grep-friendly diagnostics around the authentication branch,
provider-read start/result, fallback budget-read decision, and managed-provider
polling lifecycle in the affected useEffect flows. Log only safe state
categories and normalized error kinds; never pass raw err values, credentials,
usage payloads, or full user-identifying data to diagnostics, while preserving
the existing behavior and error handling.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/hooks/useEmbeddingBudgetState.ts`:
- Around line 160-163: Update the fallback request guard in the effect handling
managed providers in useEmbeddingBudgetState so creditsApi.getTeamUsage() runs
only when !hasUsage and !usageLoading, then include usageLoading in the effect
dependencies. Add a test covering teamUsage: null with isLoading: true and
verify no fallback request starts while the primary usage request is pending.

In `@src/openhuman/config/ops/model.rs`:
- Around line 263-267: The handlers at src/openhuman/config/ops/model.rs lines
263-267 and 352-356 currently collapse requeue failures into a successful zero
count. Update both handlers and requeue_failed_after_provider_change to preserve
a distinct failure result, while allowing the settings save to succeed; then
schedule a durable retry or return a user-visible warning when recovery fails,
and add failure-path coverage for both handlers.

---

Outside diff comments:
In `@app/src/hooks/useEmbeddingBudgetState.ts`:
- Around line 131-217: Add privacy-safe, grep-friendly diagnostics around the
authentication branch, provider-read start/result, fallback budget-read
decision, and managed-provider polling lifecycle in the affected useEffect
flows. Log only safe state categories and normalized error kinds; never pass raw
err values, credentials, usage payloads, or full user-identifying data to
diagnostics, while preserving the existing behavior and error handling.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a8f01e60-f942-4fb2-b253-dcb585d2691c

📥 Commits

Reviewing files that changed from the base of the PR and between 0031a5d and 9d3bc8c.

📒 Files selected for processing (23)
  • app/src/components/intelligence/MemoryTreeStatusPanel.test.tsx
  • app/src/components/intelligence/MemoryTreeStatusPanel.tsx
  • app/src/hooks/__tests__/useEmbeddingBudgetState.test.ts
  • app/src/hooks/useEmbeddingBudgetState.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • src/openhuman/config/ops/model.rs
  • src/openhuman/config/ops_tests.rs
  • src/openhuman/inference/embeddings/rpc.rs
  • src/openhuman/memory/queue/ops.rs
  • src/openhuman/memory/tree/tree/rpc.rs
🚧 Files skipped from review as they are similar to previous changes (19)
  • app/src/lib/i18n/it.ts
  • src/openhuman/inference/embeddings/rpc.rs
  • app/src/lib/i18n/en.ts
  • app/src/components/intelligence/MemoryTreeStatusPanel.test.tsx
  • app/src/lib/i18n/id.ts
  • app/src/components/intelligence/MemoryTreeStatusPanel.tsx
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/lib/i18n/bn.ts
  • src/openhuman/memory/queue/ops.rs
  • src/openhuman/memory/tree/tree/rpc.rs

Comment thread app/src/hooks/useEmbeddingBudgetState.ts Outdated
Comment thread src/openhuman/config/ops/model.rs Outdated
… load

Address CodeRabbit's incremental review on 9d3bc8c:

- useEmbeddingBudgetState: gate the direct getTeamUsage() fallback on
  !usageLoading. `teamUsage` is also null while useUsageState's own request is
  pending, so without this a normal managed user could fire a duplicate billing
  call. Added `usageLoading` to the effect deps.
- requeue_failed_after_provider_change now returns Result<u64, String> instead
  of collapsing a store failure into 0. All four call sites (both model.rs
  handlers, embeddings::update_settings, set_api_key) keep the settings save
  successful but report `requeued_failed=error (...)` instead of a misleading
  `=0` (the two embeddings RPCs also add a `requeue_error` JSON field). A store
  failure can no longer make a still-parked queue look remediated.

Tests: hook loading-state case (no fallback while primary usage pending);
requeue_after_provider_change_surfaces_store_errors (file-as-workspace_dir → Err,
not Ok(0)); existing requeue tests updated to the Result return.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 5, 2026

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@M3gA-Mind M3gA-Mind left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed read-only against upstream/main. The engineering here is strong and I verified rather than took on trust: the four provider-change requeue sites are each gated on an actual embedder change, requeue_failed_after_provider_change returns Err rather than a 0 that would read identically to "nothing parked" (with a real ENOTDIR failure-path test), the queue_idle_ms predicate correctly measures time since the queue last settled and takes max(last_settled, oldest_eligible) so fresh work starts its own window, i18n is complete across en + all 13 locales with no em dashes, and the new JSON fields are additive.

One thing I think blocks it, and it is a false-alarm path rather than a crash.

The managed-embeddings gate reads a field that does not govern the pipeline.

useEmbeddingBudgetState decides whether the user bills against the managed budget from loadEmbeddingsSettings().provider, which is config.memory.embedding_provider (inference/embeddings/rpc.rs:57). That field is not authoritative for whether embeddings run locally, and main already says so in two places:

  • memory/store/factories.rs:194 — "Ollama embedder regardless of what memory.embedding_provider says"
  • a test named embedding_settings_local_overrides_memory_config, asserting that a Some(local_model) "is the stronger signal and must override it"

The memory-tree embedder's real ladder (memory/tree/score/embed/factory.rs::resolve_embedder_choice) never reads memory.embedding_provider at all — it resolves to local Ollama via a memory_tree.embedding_endpoint override or via workload_local_model("embeddings"), the unified "Memory embeddings" Local AI setting. Neither path rewrites memory.embedding_provider; apply_local_ai_settings sets config.local_ai.usage.embeddings and nothing else.

So a user who enabled local embeddings through Local AI Settings runs fully local and bills nothing against the managed budget, but still reports provider === "cloud" (the default). Once their chat budget crosses 90% they get the non-dismissible, shell-mounted banner on every screen saying memory has stopped growing because the embedding budget is used up, plus a native notification at exhaustion — while their memory is growing fine.

That contradicts the PR body ("Gated on embeddings actually billing against the managed budget, so BYO-key and local-Ollama users never see a false alarm") and the hook's own principle that "a false alarm here would teach users to ignore the real one". The BYO-key half is correct; those paths do change the provider field. The local-via-Local-AI-Settings half is not.

CI is green because the managed-provider gate tests only exercise slug strings.

The fix looks cheap: the core already resolves this (EmbedderChoice / workload_local_model("embeddings")), so exposing the effective embedder on embeddings.get_settings as an additive field and gating on that would close it, and the existing gate tests would still pass as written.

Happy to re-review as soon as that is addressed — everything else in this PR reads as ready.

…budget-warnings

# Conflicts:
#	app/src/lib/i18n/ar.ts
#	app/src/lib/i18n/bn.ts
#	app/src/lib/i18n/de.ts
#	app/src/lib/i18n/en.ts
#	app/src/lib/i18n/es.ts
#	app/src/lib/i18n/fr.ts
#	app/src/lib/i18n/hi.ts
#	app/src/lib/i18n/id.ts
#	app/src/lib/i18n/it.ts
#	app/src/lib/i18n/ko.ts
#	app/src/lib/i18n/pl.ts
#	app/src/lib/i18n/pt.ts
#	app/src/lib/i18n/ru.ts
#	app/src/lib/i18n/zh-CN.ts
#	app/src/types/userError.ts
…he setting

`useEmbeddingBudgetState` decided whether a user bills against the managed
budget from `loadEmbeddingsSettings().provider`, i.e.
`config.memory.embedding_provider`. That field is not authoritative for how
embeddings are funded: the memory-tree ladder resolves local Ollama from
`memory_tree.embedding_endpoint` or from the unified
`workload_local_model("embeddings")` setting (the "Memory embeddings" toggle in
Local AI Settings), and neither path rewrites it.

So a user who enabled local embeddings that way runs fully local and bills
nothing — but still reads `provider === "cloud"`. Once their *chat* budget
crossed 90% they got the non-dismissible, shell-mounted "memory has stopped
growing" banner on every screen, plus a native notification at exhaustion,
while their memory was growing fine. That is exactly the false alarm this
feature set out to avoid. The BYO-key half was already correct — those paths do
change the provider field.

- `effective_embedder_slug(config)` walks the same `resolve_embedder_choice`
  ladder both factories walk, so it cannot drift from real resolution. Maps to
  stable wire slugs: ollama / custom / cloud / none / unconfigured / unknown.
  A ladder error reports `unknown`, never `cloud` — an unresolvable config must
  not manufacture a warning.
- `embeddings.get_settings` gains an additive `effective_provider` field.
- The hook gates on `effective_provider ?? provider`, so an older core degrades
  to the previous behaviour instead of throwing.

Also adds the privacy-safe lifecycle diagnostics CodeRabbit asked for
(auth branch, provider read start/result, fallback-read decision, polling
on/off). Error logs carry a normalized kind, never the raw error, which can
quote endpoints or backend messages.

Tests: 6 ladder cases in the embed factory (incl. the local-overrides-cloud
regression), an RPC-level assertion that the two fields can disagree, and 4
hook cases covering both gate directions, `unconfigured`, and the missing-field
fallback.

Refs tinyhumansai#5324

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

setFallbackUsage(null);
setProviderLoading(false);
return;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2c348a9 — added the privacy-safe lifecycle diagnostics.

Each branch now logs under the [embedding-budget] grep prefix: the auth branch (skip: not authenticated), provider read start (with hasUsage/usageLoading), provider read result (effective=… configured=… managed=…), the fallback-read decision in all three shapes (start / not-needed-with-reasons / session-expired), and the polling lifecycle (polling on every 60000ms / polling off / polling stopped).

Raw err values are never passed to a log. Both failure paths go through a new errorKind(err) helper that emits core_rpc:<kind> for our typed CoreRpcError, error:<name> for a plain Error, and unknown otherwise — no message, no endpoint, no usage payload, no user-identifying data. The only categories logged are provider slugs, booleans, and the poll interval.

@YellowSnnowmann

Copy link
Copy Markdown
Contributor Author

@M3gA-Mind — you're right, and the diagnosis was exact. Fixed in 2c348a9.

I checked the claim rather than taking it: resolve_embedder_choice (memory/tree/score/embed/factory.rs) never reads memory.embedding_provider. Step 1 is the memory_tree.embedding_endpoint override and step 3 is workload_local_model("embeddings") — the unified Local AI "Memory embeddings" setting — and neither rewrites that field. So a local-embeddings user still reads provider === "cloud", bills nothing, and would have been handed the non-dismissible shell-mounted banner the moment their chat budget crossed 90%. That is the exact false alarm the hook's own doc comment says must never happen, and CI could not have caught it because the gate tests only exercise slug strings.

The fix is the one you sketched — expose the effective embedder additively and gate on that:

  • effective_embedder_slug(config) (new, in the embed factory) walks the same resolve_embedder_choice ladder both existing factories walk, rather than re-deriving the rules. That was deliberate: this module's own comments call out that a read/write resolution mismatch would silently corrupt recall, and a third hand-maintained copy of the ladder would be the same class of bug. Maps to stable wire slugs ollama / custom / cloud / none / unconfigured / unknown.
  • embeddings.get_settings gains effective_provider — additive, next to the unchanged provider.
  • The hook gates on effective_provider ?? provider, so a core old enough not to send the field degrades to the previous behaviour instead of throwing.

Two judgement calls worth flagging:

  1. A ladder failure reports unknown, not cloud. resolve_embedder_choice returns Result (the OpenAI-compatible branch can fail to construct). Defaulting an unresolvable config to the managed slug would let a transient config error manufacture the very warning we are trying to stop firing falsely, so it resolves to not-managed and logs.
  2. NoProviderunconfigured, also not managed. Signed out or no usable provider means nothing is billed, so it must not read as managed even though the per-section field still defaults to cloud.

Coverage, in the layers where each failure could recur:

  • Factory: effective_slug_reports_ollama_when_local_ai_overrides_cloud_setting is your exact scenario — memory.embedding_provider = "cloud" plus the Local AI toggle asserts the field still says cloud and the slug says ollama. Plus explicit-endpoint override, real managed session, no session, opt-out, and BYO OpenAI-compatible.
  • RPC: get_settings_reports_effective_provider_separately_from_the_setting pins that the two fields can disagree on the wire — a string-only gate test can't catch a regression there.
  • Hook: both gate directions (effective_provider can turn the warning on as well as off, so it is a correction and not a mute switch), unconfigured, and the missing-field fallback.

As you predicted, the existing gate tests passed unchanged.

Also in this push: the privacy-safe lifecycle diagnostics CodeRabbit asked for (replied on that thread), and a merge of upstream/main — the conflicts were additive on both sides (UserErrorKind gained local_model_unavailable from #5354 next to memory_budget_exhausted, and each locale gained userErrors.scope.memory alongside userErrors.scope.workspace), so both sides are kept. pnpm typecheck, pnpm i18n:check, pnpm i18n:english:check (0 unexpected English), the 21 hook tests, the 22 factory tests, cargo fmt --check and cargo clippy -p openhuman -- -D warnings are all green locally.

Ready for your re-review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/inference/embeddings/rpc.rs`:
- Around line 1091-1121: Add an endpoint-level assertion in
tests/embeddings_rpc_e2e.rs for the openhuman.embeddings_get_settings JSON-RPC
call, verifying the serialized response includes the effective_provider field
with the expected value. Keep the existing handler-level test
get_settings_reports_effective_provider_separately_from_the_setting unchanged.

In `@src/openhuman/memory/tree/score/embed/factory.rs`:
- Around line 282-286: Update the error handling around
effective_embedder_slug’s ladder resolution to redact sensitive endpoint data
before logging the resolve_embedder_choice() error. Apply redact_endpoint() to
custom endpoints represented in the error chain, ensuring any added
with_context() wrapping occurs only after redaction or otherwise preserves a
sanitized diagnostic; keep the existing “unknown” fallback behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b787ba1b-674b-44a0-964c-3e82146a09ff

📥 Commits

Reviewing files that changed from the base of the PR and between d6090f6 and 2c348a9.

📒 Files selected for processing (23)
  • app/src/hooks/__tests__/useEmbeddingBudgetState.test.ts
  • app/src/hooks/useEmbeddingBudgetState.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/lib/userErrors/__tests__/classify.test.ts
  • app/src/lib/userErrors/classify.ts
  • app/src/services/api/embeddingsApi.ts
  • app/src/types/userError.ts
  • src/openhuman/inference/embeddings/rpc.rs
  • src/openhuman/memory/tree/score/embed/factory.rs
  • src/openhuman/memory/tree/score/embed/mod.rs
🚧 Files skipped from review as they are similar to previous changes (18)
  • app/src/lib/userErrors/classify.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/pt.ts
  • app/src/types/userError.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/ar.ts
  • app/src/hooks/useEmbeddingBudgetState.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/userErrors/tests/classify.test.ts

Comment thread src/openhuman/inference/embeddings/rpc.rs
Comment thread src/openhuman/memory/tree/score/embed/factory.rs
…ld on the wire

Two CodeRabbit findings on the effective-embedder change.

Endpoint leakage (CWE-532): the ladder error quotes
`memory.embedding_provider` verbatim, and in its `custom:<url>` form that
string is a full endpoint URL which may carry `user:pass@` userinfo. Configured
`cloud_providers` endpoints can reach the message the same way through the
underlying constructor's context. `redact_ladder_error` now replaces each known
endpoint substring with its `redact_endpoint` form, keeping the host so the
line stays diagnosable and keeping the reason ("dimension mismatch") intact.
Scrubbing the exact strings we already hold is precise; a generic URL-matching
pass over free text would be guesswork.

E2E: the handler-level test proved `effective_provider` exists on the return
value, not that it survives serialization. The frontend gate reads this field
off the wire and silently falls back to `provider` — the stale value behind the
original false alarm — if it stops being emitted, so
`embeddings_get_settings_returns_catalog` now asserts it is present and is one
of the documented slugs.

The redaction test asserts the unredacted error really does carry the
credentials before asserting they are gone, so it cannot pass vacuously.

Refs tinyhumansai#5324

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/memory/tree/score/embed/factory.rs`:
- Around line 267-291: Update redact_ladder_error to collect the inline custom
endpoint and configured cloud-provider endpoints, sort them by descending
length, then scrub them in that order so longer values are redacted before
strict-prefix shorter values. Add a regression test covering prefix-overlapping
endpoints and verify the resulting warning contains neither endpoint’s
unredacted suffix.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 30c6b82d-d495-495a-8e8b-1817dd342f1f

📥 Commits

Reviewing files that changed from the base of the PR and between 2c348a9 and dc9c577.

📒 Files selected for processing (2)
  • src/openhuman/memory/tree/score/embed/factory.rs
  • tests/embeddings_rpc_e2e.rs

Comment thread src/openhuman/memory/tree/score/embed/factory.rs
…e rest

Substring replacement is order-sensitive. When a short configured endpoint is a
strict prefix of a longer one — `https://embed.example.com` next to
`https://embed.example.com/v1?key=…` — scrubbing the short one first rewrites
the long one's prefix, its own replacement then finds no match, and the
credential-bearing suffix survives in the warning log.

Collect every candidate endpoint, sort by descending length, then replace.

The regression test was verified to fail without the sort: it leaves
`endpoint='embed.example.com/v1?key=super-secret'` in the rendered message. It
drives `redact_ladder_error` with a synthesized error rather than the ladder,
because the property at risk is the function's ordering contract for any
message carrying both endpoints — not which branch happens to surface a
`cloud_providers` endpoint today.

Refs tinyhumansai#5324

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@M3gA-Mind
M3gA-Mind merged commit 88895a2 into tinyhumansai:main Aug 5, 2026
20 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Aug 5, 2026
Mustaqeem66 added a commit to Mustaqeem66/openhuman that referenced this pull request Aug 5, 2026
…onflict

main gained `redact_ladder_error` + `effective_embedder_slug` and nine new
tests appended at the end of `mod tests` (tinyhumansai#5402). This branch also appends
its five `resolved_embedder` tests at the same anchor, so the two insertions
collide and the PR reports a conflict.

Move the `resolved_embedder` test block from the end of `mod tests` to just
after `none_provider_returns_inert`, well clear of main's insertion point.
Test order is irrelevant in Rust and no test body, name or assertion is
changed - the file's set of lines is identical, only their position moves.

Verified by 3-way merge against merge-base 210cd6c: 0 conflict regions,
0 lines dropped from either side.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug feature Net-new user-facing capability or product behavior. memory Memory store, memory tree, recall, summarization, and embeddings in src/openhuman/memory/. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

Memory embedding failures silently store chunks without vectors — misleading 'managed budget' banner shown instead of auth error

2 participants