Skip to content

fix(memory): gmail canonicalisation, signature unification, adaptive page retry - #132

Open
yh928 wants to merge 5 commits into
tinyhumansai:mainfrom
yh928:fix/gmail-canonical-body
Open

fix(memory): gmail canonicalisation, signature unification, adaptive page retry#132
yh928 wants to merge 5 commits into
tinyhumansai:mainfrom
yh928:fix/gmail-canonical-body

Conversation

@yh928

@yh928 yh928 commented Jul 29, 2026

Copy link
Copy Markdown

Summary

Three fixes to the Composio/Gmail sync and chunk-embedding layers, all surfaced by the same live investigation into why gmail-ingested memories were unretrievable.

  • feat(sync/gmail) — Gmail sync stored to_string_pretty(&item.raw) as document content: a MIME tree with base64 part bodies, so the mail's words never reached the store and recall could not match them. Each message now routes through the existing email::canonicalise (headers as a small block, body through email_clean::clean_body), reusing parse_message_date and falling back to the epoch when undated so a message doesn't re-chunk on every sync. Adds GmailSyncPipeline::with_executor so the host can wrap the executor with its response reshape.
  • fix(chunks) — the tree keyed per-model sidecars by {model}@{dims} while the namespace store used provider=…;model=…;dims=…. Per-signature reads are exact string matches, so one vector space was partitioned in two; a format change (not a model change) stranded live tree chunks. Writes now emit the canonical spelling (delegated to the same tinyagents formatter the namespace store uses) and reads match any spelling via signature_variants — nothing on disk is rewritten, legacy rows simply become readable again and count as coverage.
  • fix(sync) — a page the provider refuses purely for size (Gmail 413 Upstream_PayloadTooLarge) is the one provider failure a smaller request can fix; the orchestrator now halves the source's page-size argument and retries down to one item, sticky for the run. Sources opt in via page_size_arg_key (max_results for Gmail); others are unchanged.

Tests

cargo test --features sync — 1298 unit + integration tests pass. New coverage: gmail canonicalisation (5), signature parsing/equivalence/variants + legacy-spelling read-back (18 in the chunks suite), too-large-page retry (4).

Notes

Consumed by an OpenHuman core PR (durable re-embed sweep + connector-aware recall) that depends on the new EmbeddingConfig.provider field and GmailSyncPipeline::with_executor.

Summary by CodeRabbit

  • New Features

    • Added provider-aware embedding configuration and variant-based signature matching for chunk and summary embeddings (improves compatibility across legacy and canonical formats).
    • Gmail sync now saves canonical Markdown (cleaner body/thread handling) and supports provider-sized pagination via a max-results hook.
    • Added automatic retry by shrinking oversized sync page requests.
  • Bug Fixes

    • Existing embeddings/summaries remain retrievable after signature format updates.
    • More reliable Gmail recipient parsing and deterministic sent-at handling (including missing/blank content).
    • Improved cold-start database bootstrap resilience for transient I/O errors.
    • Allow max_items for Composio and RSS sources (reject for other kinds).

user and others added 3 commits July 28, 2026 16:02
Gmail sync wrote `to_string_pretty(&item.raw)` as the document content, so a
MIME tree with base64 part bodies reached the chunker: the words of the mail
were absent from the store, and no amount of recall tuning could match them.

Route each message through the email canonicaliser the tree already uses —
headers as a small block, body through `email_clean::clean_body` (reply chains
and footer boilerplate stripped). The adapter reuses `parse_message_date` and
falls back to the epoch when a message carries no date at all, so an undated
message does not rewrite its own content — and re-chunk and re-embed — on every
sync.

`GmailSyncPipeline` gains `with_executor`: the response reshape that slims a
verbose Gmail payload into one record per message lives in the host, above this
crate, and the executor is the only seam through which the sync can reach it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SRSNnqQsokuGmkbpLoLCGy
The tree keyed its per-model sidecars by `{model}@{dims}` while the namespace
store used `provider={p};model={m};dims={d}`. Every per-signature read is an
exact string match, so the two spellings partition one vector space in half: a
store that changed spelling stops seeing its own prior vectors and silently
scores against nothing. On a live workspace that stranded hundreds of tree
chunks after a format change that was never a model change.

Writes now emit the canonical spelling — delegated to the same formatter the
namespace store's providers use, so the two cannot drift apart again — and
reads match any spelling of the same space via `signature_variants`. Nothing on
disk is rewritten: a legacy row keeps its own signature and simply becomes
readable again, and it counts as coverage so the re-embed chain does not pay to
recompute a vector that is already stored.

`EmbeddingConfig` gains `provider` because the canonical spelling names it and
the tree config had no way to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SRSNnqQsokuGmkbpLoLCGy
A page the provider refuses purely for its size is the one provider failure a
*smaller request* can fix, but the orchestrator treated it like any other and
failed the run. Gmail asks for 25 full message payloads at a time, so whether a
page fits depends on what is in the mail: on one live workspace a single
oversized page stopped the source dead, and it stayed dead for nine days
because nothing retried it and nothing looked.

On a too-large refusal the orchestrator now halves the source's page-size
argument and re-requests the same page, down to one item. Sources opt in by
naming that argument (`page_size_arg_key`, `max_results` for Gmail); one that
declares none — or a page already at the floor — still surfaces the failure
rather than looping on a request that cannot change. The reduced size sticks
for the rest of the run so later pages don't each pay a rejected round-trip to
rediscover the same limit, and every attempt is still recorded against the
request budget.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SRSNnqQsokuGmkbpLoLCGy
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f327b46-18d1-4ab2-9e57-9f68fb24fc49

📥 Commits

Reviewing files that changed from the base of the PR and between 35e81c1 and 5801e80.

📒 Files selected for processing (5)
  • src/memory/chunks/connection.rs
  • src/memory/chunks/recovery.rs
  • src/memory/chunks/recovery_tests.rs
  • src/memory/sources/types.rs
  • src/memory/sources/types_tests.rs

📝 Walkthrough

Walkthrough

The PR adds canonical and legacy embedding-signature matching across chunk and summary storage, provider-aware page-size retries for incremental syncs, canonical Gmail Markdown rendering, transient database bootstrap retries, and Composio source validation.

Changes

Embedding signature compatibility

Layer / File(s) Summary
Signature contract and configuration
src/memory/config.rs, src/memory/chunks/mod.rs, src/memory/chunks/signature.rs, src/memory/chunks/signature_tests.rs
Embedding signatures support canonical and legacy spellings, equivalence checks, variant expansion, SQL IN clauses, and provider defaults.
Variant-aware embedding storage reads
src/memory/chunks/embeddings.rs, src/memory/tree/store/summaries.rs, src/memory/chunks/store_embed_tests.rs
Chunk and summary lookups, batch reads, and uncovered-work detection match recognized signature variants, including legacy stored embeddings.

Gmail synchronization resilience

Layer / File(s) Summary
Provider-aware page-size retry
src/memory/sync/composio/orchestrator.rs, src/memory/sync/composio/orchestrator_tests.rs
Incremental sources can declare page-size parameters, allowing payload-too-large responses to retry with smaller requests.
Gmail executor and canonical email adapter
src/memory/sync/composio/gmail.rs, src/memory/sync/composio/gmail_tests.rs
Gmail uses an ActionExecutor, declares max_results, and renders provider messages as canonical Markdown with tested fallbacks.

Storage bootstrap and source validation

Layer / File(s) Summary
Transient database bootstrap retry
src/memory/chunks/recovery.rs, src/memory/chunks/connection.rs, src/memory/chunks/recovery_tests.rs
Cold-start classification includes SQLITE_IOERR_FSTAT, and initialization retries matching failures with bounded exponential backoff.
Composio max-items validation
src/memory/sources/types.rs, src/memory/sources/types_tests.rs
max_items is accepted for Composio and RSS sources and rejected for other source kinds.

Estimated code review effort: 4 (Complex) | ~75 minutes

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant SyncOrchestrator
  participant GmailSyncPipeline
  participant ActionExecutor
  participant MemoryStore
  SyncOrchestrator->>GmailSyncPipeline: request page with max_results
  GmailSyncPipeline->>ActionExecutor: execute Gmail request
  ActionExecutor-->>GmailSyncPipeline: payload-too-large response
  GmailSyncPipeline->>ActionExecutor: retry with reduced max_results
  ActionExecutor-->>SyncOrchestrator: successful message page
  SyncOrchestrator->>MemoryStore: store canonical Markdown document
Loading

Suggested reviewers: senamakel

Poem

I’m a rabbit with signatures neat,
Matching old spellings beneath every seat.
Gmail pages shrink when they grow too wide,
Markdown blooms with the mail inside.
Hop, hop—canonical data arrives!

🚥 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 accurately summarizes the PR’s three main changes and is concise.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

shrink_page_size(&mut arguments, source.page_size_arg_key())
{
state.record_requests(execute_error.attempts);
page_size_override = Some(reduced);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 page_size_override set to the candidate size, not the confirmed size

page_size_override = Some(reduced) is written before the retried request is known to succeed. If multiple halving steps are needed (e.g. 25 → 12 → 6 → accepted), page_size_override is updated on each failed attempt: Some(12) after the first rejection, then Some(6) after the second. Because the final value happens to be the one that worked, subsequent pages receive the correct override — but only by coincidence of ordering. A comment or a post-success assignment (moving page_size_override = Some(…) to just after break response) would make the "sticky for the run" intent explicit and immune to any future reordering of the retry steps.

@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes three related bugs surfaced by a live investigation into why Gmail-ingested memories were unretrievable: Gmail sync now routes messages through the existing email::canonicalise pipeline instead of storing raw JSON MIME trees; embedding signature reads accept both the legacy {model}@{dims} spelling and the canonical provider=…;model=…;dims=… form so no vectors are stranded by a format change; and the sync orchestrator now halves the page-size argument and retries when the provider returns a 413 too-large response.

  • Gmail canonicalisation (gmail.rs): replaces serde_json::to_string_pretty(&item.raw) with a proper call to email::canonicalise, yielding human-readable Markdown that the chunker and recall engine can actually match against.
  • Signature unification (signature.rs, embeddings.rs, summaries.rs): introduces signature_variants / signature_in_clause so every per-signature read expands to an IN predicate covering both spellings, while writes emit only the canonical form going forward — legacy rows become readable without a migration.
  • Adaptive page retry (orchestrator.rs): detects is_payload_too_large provider failures and halves the max_results argument, retrying down to one item and keeping the reduced size sticky for the rest of the run.

Confidence Score: 5/5

Safe to merge — all three fixes are independently correct, thoroughly tested, and backward-compatible with data already on disk.

The signature-variant expansion correctly reuses SQLite numbered parameters across repeated IN clauses; the orchestrator retry loop terminates at the MIN_PAGE_SIZE floor; the Gmail canonicaliser falls back gracefully on any error path. 27 new tests cover the critical regressions and the new behaviour end-to-end. The single note is a style limit on orchestrator.rs, which does not affect correctness.

Files Needing Attention: No files require special attention for correctness; orchestrator.rs is slightly over the team's line-count guideline.

Important Files Changed

Filename Overview
src/memory/chunks/signature.rs New module — implements signature parsing, equivalence checks, and signature_variants/signature_in_clause to bridge the legacy {model}@{dims} and canonical provider=…;model=…;dims=… spellings. Well-documented with thorough tests.
src/memory/chunks/embeddings.rs All per-signature reads now use IN (?n, ?n+1, …) via signature_variants; writes continue to use the canonical form. The has_uncovered_reembed_work query reuses numbered SQLite parameters correctly across four IN clauses.
src/memory/sync/composio/gmail.rs Gmail sync now produces canonical Markdown via email::canonicalise instead of raw JSON; adds with_executor seam and page_size_arg_key. Fallback chain in message_body and deterministic epoch default in message_sent_at are correct.
src/memory/sync/composio/orchestrator.rs Adds adaptive page-size retry loop for 413 responses; retry terminates correctly because shrink_page_size returns None at the floor. File is now 545 lines, exceeding the project's 500-line guideline.
src/memory/tree/store/summaries.rs Summary-embedding reads now use signature_variants / signature_in_clause in both single and batch fetch paths, mirroring the same fix applied to chunk embeddings.
src/memory/config.rs Adds provider field to EmbeddingConfig with serde(default = "default_embedding_provider") ("ollama"), matching the existing default model; existing configs without the field deserialize transparently.
src/memory/chunks/connection.rs Splits open_and_init into a retry wrapper + try_open_and_init; retries up to 3 times with exponential backoff on is_transient_cold_start errors including the newly added SQLITE_IOERR_FSTAT.
src/memory/chunks/recovery.rs Adds SQLITE_IOERR_FSTAT (1802) to the transient cold-start error set; covered by a new test in recovery_tests.rs.
src/memory/sources/types.rs Extends max_items validation to accept SourceKind::Composio alongside RssFeed, fixing a UI desync where editing a Composio source's item cap was rejected.
src/memory/chunks/signature_tests.rs New test file with 8 tests covering parse, equivalence, variant expansion, dedup, and IN-clause generation — comprehensive coverage for the new signature module.
src/memory/sync/composio/orchestrator_tests.rs New test file: SizeLimitedExecutor simulates a provider ceiling; covers the halving sequence, no-shrink fast-fail, is_payload_too_large discrimination, and floor behavior.
src/memory/sync/composio/gmail_tests.rs New test file: 5 tests covering canonical Markdown output, body fallback chain, blank-field skip, recipient splitting, and deterministic epoch for undated messages.
src/memory/chunks/store_embed_tests.rs Adds an end-to-end regression test: a vector written under the legacy spelling is readable, counts as covered, and does not block a genuinely different space — all three hot paths verified.
src/memory/sources/types_tests.rs New test guards the max_items Composio validation fix: checks that Composio and RSS accept it while Folder, GithubRepo, and WebPage reject it.
src/memory/chunks/recovery_tests.rs Adds a test confirming IOERR_FSTAT (1802) is classified as transient and that a non-transient error (code 19) is not.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Build arguments from source.arguments] --> B{page_size_override set?}
    B -->|Yes| C[apply_page_size: pin key to override]
    B -->|No| D[Use source default]
    C --> E[executor.execute]
    D --> E
    E --> F{Transport error?}
    F -->|Yes, tolerated| G[Warn, break to next scope]
    F -->|Yes, fatal| H[return Err]
    F -->|No| I[record_action cost+attempts]
    I --> J{response.successful?}
    J -->|Yes| K[break to process items]
    J -->|No| L{is_payload_too_large?}
    L -->|No| M{tolerate_scope_errors?}
    M -->|Yes| G
    M -->|No| H
    L -->|Yes| N{shrink_page_size possible?}
    N -->|No, at floor| M
    N -->|Yes| O[Halve page size in arguments, set page_size_override]
    O --> P{budget_exhausted?}
    P -->|Yes| Q[mark more_pending, break scopes]
    P -->|No| E
    K --> R[Extract and dedup items]
    R --> S{More pages?}
    S -->|Yes, next token| A
    S -->|No| T[Done with scope]
Loading

Reviews (2): Last reviewed commit: "fix(chunks): retry a transient cold-star..." | Re-trigger Greptile

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/memory/chunks/embeddings.rs (1)

456-507: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Split embeddings query helpers before the 500-line cap.

src/memory/chunks/embeddings.rs is now 518 lines, so extract the signature/embedding query helpers into a focused sibling module such as src/memory/chunks/embeddings_query.rs.

🤖 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 `@src/memory/chunks/embeddings.rs` around lines 456 - 507, Extract the
signature/embedding query helpers, including
get_chunk_embeddings_for_signature_batch and its supporting query logic, from
embeddings.rs into a focused sibling module such as embeddings_query.rs. Update
module declarations and imports so existing callers retain the same behavior and
public API, and keep embeddings.rs below the 500-line limit.

Source: Coding guidelines

src/memory/sync/composio/gmail.rs (1)

280-296: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cc recipients are always dropped from the canonical email.

cc is hardcoded to Vec::new() (Line 288) even though email::canonicalise renders a Cc: line when present. If the upstream Gmail response reshaper surfaces Cc data on the message payload, it's silently lost here, meaning content someone was Cc'd on won't be searchable via that header. Worth confirming whether the reshaped envelope carries Cc data at all, and if so, extracting it the same way to is handled.

🤖 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 `@src/memory/sync/composio/gmail.rs` around lines 280 - 296, The email_thread
function discards Cc recipients by always assigning an empty vector. Confirm the
reshaped Gmail message payload’s Cc field and extract it using the same
recipient parsing approach as message_recipients for to, preserving the
populated Cc values in EmailMessage.
🤖 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/memory/sync/composio/orchestrator.rs`:
- Around line 497-545: Split the page-size retry helpers MIN_PAGE_SIZE,
is_payload_too_large, apply_page_size, and shrink_page_size out of
orchestrator.rs into a focused sibling module such as page_size.rs, re-exporting
or importing them for existing callers. Move their tests into a dedicated
page_size_tests.rs module while preserving current behavior and keeping
orchestrator.rs below the 500-line limit.
- Around line 508-517: Update is_payload_too_large so the 413 detection only
matches status-code-like occurrences rather than any unanchored “413” substring.
Preserve the existing payloadtoolarge, payload_too_large, and too-large
payload/response checks while preventing unrelated identifiers, amounts, or
timestamps from triggering retries.

---

Nitpick comments:
In `@src/memory/chunks/embeddings.rs`:
- Around line 456-507: Extract the signature/embedding query helpers, including
get_chunk_embeddings_for_signature_batch and its supporting query logic, from
embeddings.rs into a focused sibling module such as embeddings_query.rs. Update
module declarations and imports so existing callers retain the same behavior and
public API, and keep embeddings.rs below the 500-line limit.

In `@src/memory/sync/composio/gmail.rs`:
- Around line 280-296: The email_thread function discards Cc recipients by
always assigning an empty vector. Confirm the reshaped Gmail message payload’s
Cc field and extract it using the same recipient parsing approach as
message_recipients for to, preserving the populated Cc values in EmailMessage.
🪄 Autofix (Beta)

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 Plus

Run ID: 5ba9d842-8a29-4ad2-8ac1-c0ead2d8e11b

📥 Commits

Reviewing files that changed from the base of the PR and between e5753bb and 35e81c1.

📒 Files selected for processing (11)
  • src/memory/chunks/embeddings.rs
  • src/memory/chunks/mod.rs
  • src/memory/chunks/signature.rs
  • src/memory/chunks/signature_tests.rs
  • src/memory/chunks/store_embed_tests.rs
  • src/memory/config.rs
  • src/memory/sync/composio/gmail.rs
  • src/memory/sync/composio/gmail_tests.rs
  • src/memory/sync/composio/orchestrator.rs
  • src/memory/sync/composio/orchestrator_tests.rs
  • src/memory/tree/store/summaries.rs

Comment on lines +497 to +545

/// Smallest page a shrink will ask for. One item is the point past which a
/// too-large response is about that single item, not the batch size.
const MIN_PAGE_SIZE: u64 = 1;

/// Whether the provider refused a page for its *size* rather than for anything
/// about the request's content — the only failure a smaller page can fix.
///
/// Matched on the error text because that is all the envelope carries: Composio
/// reports it as HTTP 413 with a `Upstream_PayloadTooLarge` slug, and other
/// backends phrase it as "payload too large" / "response too large".
fn is_payload_too_large(error: Option<&str>) -> bool {
error.is_some_and(|error| {
let lower = error.to_ascii_lowercase();
lower.contains("payloadtoolarge")
|| lower.contains("payload_too_large")
|| lower.contains("413")
|| (lower.contains("too large")
&& (lower.contains("payload") || lower.contains("response")))
})
}

/// Pin the page-size argument to `size`, if the source declared one.
fn apply_page_size(arguments: &mut Value, key: Option<&str>, size: Option<u64>) {
if let (Some(key), Some(size)) = (key, size) {
if let Some(slot) = arguments.get_mut(key) {
*slot = Value::from(size);
}
}
}

/// Halve the page-size argument in place, returning the new value.
///
/// `None` means retrying is pointless — the source declares no page-size
/// argument, this request does not carry it, or it is already at the floor.
fn shrink_page_size(arguments: &mut Value, key: Option<&str>) -> Option<u64> {
let key = key?;
let current = arguments.get(key)?.as_u64()?;
if current <= MIN_PAGE_SIZE {
return None;
}
let reduced = (current / 2).max(MIN_PAGE_SIZE);
arguments[key] = Value::from(reduced);
Some(reduced)
}

#[cfg(test)]
#[path = "orchestrator_tests.rs"]
mod tests;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

File exceeds the 500-line cap introduced by this diff.

The new retry helpers and mod tests wiring push this file to roughly 545 lines, past the guideline's 500-line threshold. Consider extracting the page-size retry helpers (MIN_PAGE_SIZE, is_payload_too_large, apply_page_size, shrink_page_size) into a focused sibling module (e.g. page_size.rs) re-exported here, with its own page_size_tests.rs. As per path instructions, "Avoid letting any source file grow beyond 500 lines; split behavior into focused modules before that point."

🤖 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 `@src/memory/sync/composio/orchestrator.rs` around lines 497 - 545, Split the
page-size retry helpers MIN_PAGE_SIZE, is_payload_too_large, apply_page_size,
and shrink_page_size out of orchestrator.rs into a focused sibling module such
as page_size.rs, re-exporting or importing them for existing callers. Move their
tests into a dedicated page_size_tests.rs module while preserving current
behavior and keeping orchestrator.rs below the 500-line limit.

Source: Path instructions

Comment on lines +508 to +517
fn is_payload_too_large(error: Option<&str>) -> bool {
error.is_some_and(|error| {
let lower = error.to_ascii_lowercase();
lower.contains("payloadtoolarge")
|| lower.contains("payload_too_large")
|| lower.contains("413")
|| (lower.contains("too large")
&& (lower.contains("payload") || lower.contains("response")))
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Bare "413" substring match risks false positives.

Matching any error text containing "413" anywhere (Line 513) can misclassify unrelated failures whose message happens to contain that numeral (an id, amount, timestamp, etc.) as a too-large-page error, triggering pointless shrink/retry cycles before the real error is finally surfaced. Consider requiring "413" to appear as a status-code-like token (e.g. anchored with word boundaries, or only checked alongside an HTTP-status-shaped prefix) rather than an unanchored substring.

🩹 Tighten the 413 check
-        lower.contains("payloadtoolarge")
-            || lower.contains("payload_too_large")
-            || lower.contains("413")
-            || (lower.contains("too large")
-                && (lower.contains("payload") || lower.contains("response")))
+        lower.contains("payloadtoolarge")
+            || lower.contains("payload_too_large")
+            || lower
+                .split(|c: char| !c.is_ascii_alphanumeric())
+                .any(|token| token == "413")
+            || (lower.contains("too large")
+                && (lower.contains("payload") || lower.contains("response")))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn is_payload_too_large(error: Option<&str>) -> bool {
error.is_some_and(|error| {
let lower = error.to_ascii_lowercase();
lower.contains("payloadtoolarge")
|| lower.contains("payload_too_large")
|| lower.contains("413")
|| (lower.contains("too large")
&& (lower.contains("payload") || lower.contains("response")))
})
}
fn is_payload_too_large(error: Option<&str>) -> bool {
error.is_some_and(|error| {
let lower = error.to_ascii_lowercase();
lower.contains("payloadtoolarge")
|| lower.contains("payload_too_large")
|| lower
.split(|c: char| !c.is_ascii_alphanumeric())
.any(|token| token == "413")
|| (lower.contains("too large")
&& (lower.contains("payload") || lower.contains("response")))
})
}
🤖 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 `@src/memory/sync/composio/orchestrator.rs` around lines 508 - 517, Update
is_payload_too_large so the 413 detection only matches status-code-like
occurrences rather than any unanchored “413” substring. Preserve the existing
payloadtoolarge, payload_too_large, and too-large payload/response checks while
preventing unrelated identifiers, amounts, or timestamps from triggering
retries.

user and others added 2 commits July 29, 2026 14:49
The host UI exposes `max_items` for Composio connections and creates them with
a toolkit default, but `validate_for_kind` accepted it only for RSS feeds — so
editing a Composio source's per-run cap failed with "field 'max_items' is not
applicable to source kind 'composio'". Accept it for Composio too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SRSNnqQsokuGmkbpLoLCGy
Parallel first-opens of the chunk DB race each other on the -shm/-wal
side-files, surfacing as flaky disk I/O errors (IOERR_FSTAT 1802, plus the
CANTOPEN/SHM* codes already classified). A fresh open a moment later succeeds,
so open_and_init now gives a transient cold-start a couple of short backed-off
retries before surfacing it, and IOERR_FSTAT joins the transient set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SRSNnqQsokuGmkbpLoLCGy
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