Skip to content

[fix] Bound the session-records fetch and consume the persist-drop signal - #5501

Merged
ardaerzin merged 6 commits into
release/v0.106.1from
fix/sessions-records-robustness
Jul 29, 2026
Merged

[fix] Bound the session-records fetch and consume the persist-drop signal#5501
ardaerzin merged 6 commits into
release/v0.106.1from
fix/sessions-records-robustness

Conversation

@ardaerzin

Copy link
Copy Markdown
Contributor

Context

Two small robustness gaps in the last-message-only reconstruction pipeline (#5486), separate from the QA-finding PRs (#5488-#5491, #5494).

Changes

  • Records fetch had no timeout. fetchSessionRecords sits on the turn's critical path (the prompt waits on it), so a stalled query would hang on Undici's long request-level timeout instead of falling back to the inbound history. Added an env-tunable AbortSignal.timeout (AGENTA_SESSIONS_RECORDS_QUERY_TIMEOUT_MS, 5s default).
  • The persist-drop signal was never consumed. takePersistFailures had no caller: in durable mode a dropped record was counted into a per-session map that nothing read, so the signal was lost and the map grew unbounded. Now consumed at the turn-end drain (flush) — it warns when the durable log is incomplete and clears the counter.

Tests

  • Runner 1264 unit tests + typecheck green. Added a flush-consumer test (warns + clears) and switched the existing durable-count test to drain directly, since flush now consumes the count.

Note

Neither is a QA finding; both surfaced while reviewing the reconstruction path. Independent of the other stacked PRs — merges into #5486 in any order.

Two small robustness gaps in the reconstruction pipeline:

- fetchSessionRecords sits on the turn's critical path (the prompt waits on it), but had no
  timeout, so a stalled query would wait on Undici's long request-level timeout instead of
  failing fast to the inbound-history fallback. Add an env-tunable AbortSignal.timeout (5s
  default).
- takePersistFailures had no caller: in durable mode a dropped record was counted into a
  per-session map that nothing read, so the signal was lost and the map grew unbounded.
  Consume it at the turn-end drain (flush) — warn when the durable log is incomplete and
  clear the counter.
@dosubot dosubot Bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Jul 25, 2026
@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jul 29, 2026 5:46pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 29d34144-7f0b-42ed-ac5a-e80c2cc9ec99

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Runner persistence adds bounded environment-configured retries and turn-end dropped-record warnings. Shared environment helpers clamp numeric and timer values. Session record queries gain abort timeouts, and sandbox run limits normalize timer delays.

Changes

Session reliability

Layer / File(s) Summary
Numeric environment configuration
services/runner/src/env.ts, services/runner/src/engines/sandbox_agent/run-limits.ts, services/runner/tests/unit/env-num.test.ts, services/runner/tests/unit/run-limits.test.ts
Adds integer and timer environment readers with bounded values and once-per-variable warnings; sandbox run limits clamp all resolved delays to timer-safe values.
Durable ingest and flush accounting
services/runner/src/sessions/persist.ts, services/runner/tests/unit/session-persist.test.ts
Durable retries and open-tool TTLs use shared environment parsing; turn-end flush consumes dropped-record counts and emits a warning when durable records were dropped.
Records query timeout
services/runner/src/sessions/records-query.ts, services/runner/tests/unit/session-records-query.test.ts
Session record fetches use bounded environment-configured abort timeouts, with tests for undersized and oversized overrides.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SessionTurn
  participant buildPersistingEmitter
  participant drainPersist
  participant takePersistFailures
  participant stderr
  SessionTurn->>buildPersistingEmitter: flush()
  buildPersistingEmitter->>drainPersist: await pending durable records
  drainPersist-->>buildPersistingEmitter: drain complete
  buildPersistingEmitter->>takePersistFailures: consume session drop count
  takePersistFailures-->>buildPersistingEmitter: dropped count
  buildPersistingEmitter->>stderr: emit WARN when count > 0
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title is concise and accurately captures the two main changes: session-records timeout bounding and persist-drop consumption.
Description check ✅ Passed The description is clearly related to the changeset and matches the timeout and durable-drop handling updates.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sessions-records-robustness

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.

@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Preview URL https://gateway-production-866a.up.railway.app/w
Project agenta-oss-pr-5501
Image tag pr-5501-20f2e3e
Status Deployed
Railway logs Open logs
Workflow logs View workflow run
Updated at 2026-07-29T17:55:44.599Z

@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: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 452202da-8419-4099-beb1-53772089ca5d

📥 Commits

Reviewing files that changed from the base of the PR and between 882eebc and 35fbe66.

📒 Files selected for processing (3)
  • services/runner/src/sessions/persist.ts
  • services/runner/src/sessions/records-query.ts
  • services/runner/tests/unit/session-persist.test.ts

Comment thread services/runner/src/sessions/records-query.ts
The records-fetch timeout added in this branch could resolve to a value that aborts the
request instantly — the opposite of what the bound is for. AGENTA_SESSIONS_RECORDS_QUERY_
TIMEOUT_MS=0.5 passes the `> 0` check, truncates to 0, and AbortSignal.timeout(0) rejects
before the query completes, silently pinning reconstruction to the inbound-history fallback.
The same read had no ceiling either: above 2^31-1 Node's timer overflows to a 1ms delay, and
above 2^32-1 AbortSignal.timeout throws outright.

That read was the fourth hand-rolled numeric-env parser in the runner, each encoding a
different partial idea of what is valid, and every failure mode degrades toward no protection
at all. State the domain once instead: src/env.ts reads a name as an integer inside [min, max],
truncating before clamping (so a fractional value cannot sneak through as 0) and capping timer
delays at Node's 32-bit ceiling. Unset stays silent; set-but-unusable falls back or clamps and
warns once, since a typo in one env var must not break every run in the process.

Routed through it:
- records-query: the timeout above, floor and ceiling both.
- persist: OPEN_TOOL_TTL_MS carried the same hole in NaN form — junk parsed to NaN and
  setTimeout(fn, NaN) fires at 1ms, collapsing the tool-call coalescing window this file
  exists to maintain. durableMaxRetries gains a ceiling, since each extra attempt doubles
  the worst-case turn-end drain.
- run-limits: drops its private envMs, which accepted 0 (a deadline that trips immediately)
  and had no integer or overflow bound.

Tests: env-num covers each input regime; session-records-query pins the bound at the call
site, which had no coverage at all. Runner unit suite 1277 green, typecheck clean.
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels Jul 29, 2026
@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 1

Caution

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

⚠️ Outside diff range comments (2)
services/runner/src/sessions/persist.ts (2)

86-120: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound ingest request timeouts separately from retry attempts.

drainPersist() awaits the in-flight persist chain, but the fetch here has no abort signal. A never-settling request can hold the chain indefinitely instead of hitting the retry loop, so use a per-attempt deadline such as AbortSignal.timeout(...) for each fetch and add coverage for a fetch that never settles.


84-96: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Make every persisted ingest idempotent.

Only tool_call, tool_result, interaction_request, interaction_response, and persisted out-of-band records get a stable record_id; coalesced message and thought records do. When retries resend without that ID, the documented upsert contract cannot protect the record. Send a deterministic record ID for all retried events, or add database/proxy deduplication based on session_id + record_index before relying on the lost-response durability guarantee.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 80ab041d-3d19-4ab9-be4d-593eae25c34d

📥 Commits

Reviewing files that changed from the base of the PR and between 35fbe66 and 2a0c194.

📒 Files selected for processing (6)
  • services/runner/src/engines/sandbox_agent/run-limits.ts
  • services/runner/src/env.ts
  • services/runner/src/sessions/persist.ts
  • services/runner/src/sessions/records-query.ts
  • services/runner/tests/unit/env-num.test.ts
  • services/runner/tests/unit/session-records-query.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/runner/src/sessions/records-query.ts

Comment thread services/runner/src/engines/sandbox_agent/run-limits.ts
resolveRunLimits derives `idle` as half the total when a misconfigured idle would otherwise be
unreachable. envTimerMs guarantees each limit is at least 1ms, but half of a total sitting at
that floor rounds to 0, and a 0ms idle timer trips the run the moment it is armed — the guard
this branch just added, escaping through a value the env reader never saw.

Guarding the derivation would leave the next one exposed, so the contract moves onto the
output: ResolvedRunLimits now states that every field is armable, and resolveRunLimits
normalizes the whole record before returning it, which covers any future cross-limit
adjustment for free. clampTimerMs is the shared primitive for that — env.ts already owned the
domain for reads, and now exposes it for delays the code computes afterwards (a fraction of
another limit, a backoff, a remaining budget). envInt clamps through the same path, so the
domain has one definition rather than two that can drift.

While wiring it: ±Infinity now clamps to the matching end of the range instead of the floor.
NaN is the only input with no magnitude to clamp toward, and an infinite deadline collapsing
to "fires instantly" is the exact inversion this is meant to prevent.

Tests: the degenerate-total case is pinned in run-limits (it fails with idleMs=0 without the
normalization), clampTimerMs is covered directly. Runner unit suite 1281 green, typecheck clean.
@ardaerzin
ardaerzin changed the base branch from feat/sessions-last-message-only to release/v0.106.1 July 29, 2026 10:27
@ardaerzin
ardaerzin merged commit 762e9cd into release/v0.106.1 Jul 29, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants