FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(sixtyfour,enrow): stop nulling model-supplied struct, retry transient poll failures by waleedlatif1 · Pull Request #7261 · simstudioai/sim · GitHub

fix(sixtyfour,enrow): stop nulling model-supplied struct, retry transient poll failures - #7261

Closed
waleedlatif1 wants to merge 8 commits into
stagingfrom
fix/sixtyfour-enrow-integrity
Closed

fix(sixtyfour,enrow): stop nulling model-supplied struct, retry transient poll failures#7261
waleedlatif1 wants to merge 8 commits into
stagingfrom
fix/sixtyfour-enrow-integrity

Conversation

waleedlatif1 commented Aug 29, 2026
edited
Loading

Copy link
Copy Markdown
Collaborator

Three reported defects across the SixtyFour and Enrow integrations. Two were real and are fixed here; the third is real but the corrected value is not derivable from public sources, so the number is left alone and only its (false) citation is corrected.

1. SixtyFour nulled a model-supplied required field — VERIFIED, FIXED

SixtyfourBlock.tools.config.params wrote two renames unconditionally:

result.struct = params.leadStruct      // enrich_lead
result.struct = params.companyStruct   // enrich_company

On the canvas path that is fine — leadStruct is a real subBlock. On the agent path it is not. transformBlockTool (apps/sim/providers/utils.ts) builds a paramsTransform that runs result = { ...result, ...blockParamsFn(result) } over the model's tool-call arguments, and the LLM schema is derived from toolConfig.params, which names the field struct, not leadStruct. So the model sends struct, the mapper reads a leadStruct that was never there, and writes struct: undefined straight over the model's value. struct is required: true on both tools, so the request went out to api.sixtyfour.ai with the required field missing.

Fix: guard every rename on its source being present. A configured block value still wins, because it is present.

Proof is apps/sim/blocks/blocks/sixtyfour.test.ts, which drives the real transformBlockTool with the real block and tool configs — not a hand-rolled imitation of the agent path.

2. Enrow aborted a polling job on any transient 5xx — VERIFIED, FIXED

Both enrow_find_email and enrow_verify_email polled with if (!pollResponse.ok) throw. A single 500 ended a job that was still running server-side — and Enrow documents 500 as a retrieval failure on the result endpoint ("could not retrieve single search results"), and documents that retrieving a result consumes no credits, so retrying is both free and correct.

Both polls now share apps/sim/tools/enrow/poll.ts, which:

  • retries only 429 and 5xx; every other non-2xx still fails on the first response
  • caps transient retries at 3 for the whole poll (a total, not per-attempt) — bounded, no unbounded loop
  • paces with backoffWithJitter(attempt, parseRetryAfter(header)) from @sim/utils/retry, honouring Retry-After
  • charges the backoff against the existing 120s MAX_POLL_TIME_MS budget rather than extending it, so worst-case wall clock is unchanged

3. Enrow hosted-credit rate cites a plan that does not exist — VERIFIED, NUMBER NOT CHANGED

ENROW_CREDIT_USD = 0.012 was documented as "Enrow's Starter plan is $24/month for 2,000 finder credits/month". No such plan exists. Enrow's published monthly tiers (https://enrow.io/pricing) are:

Plan Price Credits Per credit
Start $17/mo 1,000 $0.017
Pro $87/mo 10,000 $0.0087
Scale $397/mo 50,000 $0.00794
Custom $1,000+/mo unlimited n/a

plus a 40% annual discount. 0.012 matches none of them, monthly or annual.

The number is unchanged and this PR does not alter anyone's bill. Which rate is correct depends entirely on the plan the hosted ENROW_API_KEY_* keys are actually enrolled on, which is not a public fact. Only the docblock is corrected, to state the real price list and to say plainly that the rate corresponds to no published tier.

Billing exposure if someone later "corrects" it from the price list alone:

  • if the hosted keys sit on Start ($0.017/credit), the current $0.012 under-bills by ~29% — Sim absorbs the difference
  • if they sit on Pro ($0.0087) the current rate over-bills by ~38%; on Scale ($0.00794) it over-bills by ~51% — customers are charged above cost

To pin this correctly I need one of: the Enrow plan on the hosted account (billing portal or invoice), or the contracted per-credit rate if it is a Custom tier. Until then, changing the number is a coin flip in both directions and is deliberately not done here.

Testing

bun run lint, bun run check:audits (39/39), check-block-registry, and type-check are all clean. tool-metadata:check passes — no params/outputs changed, so no artifact regeneration was needed.

Every fix has a test that was watched fail against the pre-fix code and pass after:

  • reverting sixtyfour.ts puts both agent-path assertions red (expected undefined to be '{"website":"Company website URL"}') while the four canvas-path tests stay green — i.e. the tests pin the agent path specifically, not the mapper's shape
  • reverting the Enrow poll rewiring puts all four new retry tests red (Enrow find-email poll error: 503, expected 4 calls, got 1, and the same on the verify poll), while the existing 202/401/window tests stay green — the 4xx no-retry test passes both before and after, which is the point

Follow-up: second false citation in the same file (de50aca)

A validation audit against Enrow's published API docs found the rate-limit comment in the same hosting.ts was wrong in the same way ENROW_CREDIT_USD's was — it claimed "Enrow rate limit is ~50 req/s". Enrow documents 10 req/s per API key on every POST endpoint (rate-limits) — 600/min, not 3,000.

requestsPerMinute: 60 is unchanged and stays correct: it is conservative against either figure, and the reason for it (not bursting into the limit while a job polls) is untouched. Only the number a future reader would size the cap against was wrong.

Two audit findings worth recording

The retry design matches Enrow's own guidance exactly. The rate-limit page recommends "exponential backoff… default maximum of 3 retries" and shows 1s / 2s / 4s. This PR implements precisely that — MAX_TRANSIENT_RETRIES = 3 with backoffWithJitter at baseMs: 1000. The retryable set is confirmed too: 429 is documented there, 500 "Could not retrieve single search results" is documented on the result endpoints, and 400 ("id missing in the URL query string") is correctly not retried. Critically, both result endpoints state verbatim that "Retrieving a result does not consume credits — only the verification itself does", so a retry is provably free.

SixtyFour's struct: the prose docs and the OpenAPI schema disagree. The company-intelligence prose lists struct as optional, but the OpenAPI schema lists it under required. The tool's required: true is correct and the prose is wrong — which matters, because struct being required is exactly what made the unguarded result.struct = undefined overwrite a data-loss bug rather than a cosmetic one.

Audit also confirmed no other subBlock-id-vs-tool-param mismatch in that mapper: all 20 block inputs map 1:1 to tool param names except six deliberate remaps (emailInput→email, phoneInput→phone, leadStruct/companyStruct→struct, companyLeadStruct→leadStruct, *ResearchPlan→researchPlan), and every one is now guarded.

…ient poll failures

Sixtyfour's block param mapper wrote `result.struct = params.leadStruct`
unconditionally. On the agent path the model supplies the *tool* param name
(`struct`), not the subBlock name (`leadStruct`), so the mapper's return —
which is overlaid on the model's arguments — set the required `struct` to
`undefined` and the call went out without it. Same for `enrich_company`.
Every rename is now guarded on its source being present; a configured block
value still wins because it is present.

Enrow's find/verify polls threw on any non-2xx, so a single transient 5xx
killed a job that was still running. Both polls now share `poll.ts`, which
retries 429 and 5xx up to three times for the whole poll using
`backoffWithJitter`/`parseRetryAfter`, charging the backoff against the
existing 120s budget rather than extending it. Non-transient statuses still
fail immediately.

Also corrects the `ENROW_CREDIT_USD` docblock, which cited a $24/2,000-credit
Starter plan that does not exist on Enrow's price list. The rate itself is
left alone — see the PR body.

vercel Bot commented Aug 29, 2026
edited
Loading

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Aug 29, 2026 5:45am

greptile-apps Bot commented Aug 29, 2026
edited
Loading

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR preserves model-supplied SixtyFour struct parameters and consolidates Enrow polling with bounded transient retries and deadline-aware response handling.

  • Guards operation-specific SixtyFour parameter remaps against absent or empty configured values.
  • Shares Enrow polling behavior across find and verify operations, including retry, timeout, response-body cleanup, and explicit failed tool responses.
  • Corrects Enrow pricing and rate-limit documentation without changing billing or throttling values.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/blocks/blocks/sixtyfour.ts Guards operation-specific remaps so absent canvas values cannot overwrite model-supplied tool parameters.
apps/sim/blocks/blocks/sixtyfour.test.ts Exercises agent and canvas parameter transformation paths, including null, empty, false, and configured values.
apps/sim/tools/enrow/poll.ts Implements shared deadline-bounded polling, transient retries, body cleanup, and timeout-aware decoding.
apps/sim/tools/enrow/find_email.ts Delegates polling to the shared helper and returns an explicit failed ToolResponse when polling fails.
apps/sim/tools/enrow/verify_email.ts Applies the same shared polling and explicit failure-response behavior to verification jobs.
apps/sim/tools/enrow/find_email.test.ts Covers retry limits, Retry-After, wall-clock deadlines, body aborts, resource cleanup, and failed response propagation.
apps/sim/tools/enrow/hosting.ts Corrects explanatory pricing and rate-limit documentation without changing runtime configuration.

Sequence Diagram

sequenceDiagram
  participant Caller
  participant Tool as Enrow Tool
  participant Poll as pollEnrowJob
  participant API as Enrow API
  Caller->>Tool: Execute find/verify
  Tool->>Poll: Poll submitted job id
  loop Within 120-second budget
    Poll->>API: GET result with deadline signal
    alt 202 in progress
      API-->>Poll: 202
    else 429 or 5xx within retry cap
      API-->>Poll: transient error
      Poll->>Poll: bounded backoff
    else 200 complete
      API-->>Poll: result body
      Poll-->>Tool: decoded result
    else terminal error or deadline
      Poll-->>Tool: throw poll failure
      Tool-->>Caller: success: false with job id
    end
  end
Loading

Reviews (8): Last reviewed commit: "fix(enrow): return a failed tool respons..." | Re-trigger Greptile

Comment thread apps/sim/tools/enrow/poll.ts Outdated

cubic-dev-ai 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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

5 issues found across 7 files

Confidence score: 2/5

  • apps/sim/tools/enrow/poll.ts does not enforce MAX_POLL_TIME_MS while awaiting a hanging fetch, so an Enrow request can block beyond the polling budget — add a real deadline and abort each request when time expires.
  • apps/sim/tools/enrow/poll.ts abandons response bodies on 202/retryable responses, which can retain pooled HTTP connections across polling jobs — cancel or drain the body before retrying.
  • apps/sim/tools/enrow/poll.ts can stop up to 15 seconds early when a transient response arrives near the deadline, reducing the intended polling window — wait for the remaining budget before terminating.
  • apps/sim/tools/enrow/find_email.test.ts does not verify Retry-After timing, and apps/sim/tools/enrow/hosting.ts cites the wrong documentation endpoint; assert the delay and link the verifier documentation separately.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/sim/tools/enrow/hosting.ts">

<violation number="1" location="apps/sim/tools/enrow/hosting.ts:13">
P3: The citation points to the email-finder endpoint, not the verifier documentation, so it does not substantiate the adjacent 0.25-credit claim. Link the verifier endpoint separately.</violation>
</file>

<file name="apps/sim/tools/enrow/poll.ts">

<violation number="1" location="apps/sim/tools/enrow/poll.ts:63">
P1: If an Enrow poll request hangs or exceeds the remaining budget, `await fetch` never rechecks `elapsed`, so `MAX_POLL_TIME_MS` does not bound this operation. Track a real deadline and abort each request when the remaining budget expires.</violation>

<violation number="2" location="apps/sim/tools/enrow/poll.ts:67">
P2: When a 202 or retryable response has a body, this loop abandons it without draining or cancelling the stream, which can retain pooled HTTP connections across polling jobs. Cancel the response body before each retry or polling `continue`.</violation>

<violation number="3" location="apps/sim/tools/enrow/poll.ts:77">
P2: When a transient response arrives late in the polling window, this check breaks before `delayMs` is slept, so polling can end up to 15 seconds before the 120-second deadline. Wait out the remaining budget before terminating instead of breaking on the projected elapsed value.</violation>
</file>

<file name="apps/sim/tools/enrow/find_email.test.ts">

<violation number="1" location="apps/sim/tools/enrow/find_email.test.ts:164">
P2: This test's name claims Retry-After is honored, but it never asserts the backoff delay. `sleep` is mocked to resolve immediately, so the test only proves a 429 is retried (already covered by the 5xx test) — a regression that ignores the Retry-After header and falls back to exponential backoff would still pass. Assert that the mocked `sleep` was called with the Retry-After-derived delay (parseRetryAfter('2') clamps to 2000ms), e.g. expect(sleep).toHaveBeenCalledWith(expect.any(Function)) won't work; instead capture sleep calls via vi.mocked and check the delay argument, or export/verify the computed delay.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread apps/sim/tools/enrow/poll.ts Outdated
Comment thread apps/sim/tools/enrow/poll.ts Outdated
Comment thread apps/sim/tools/enrow/find_email.test.ts Outdated
Comment thread apps/sim/tools/enrow/poll.ts Outdated
Comment thread apps/sim/tools/enrow/hosting.ts Outdated
…e abandoned bodies

Review follow-ups on the polling helper.

The budget was accounted purely in intended sleep time, which `await fetch`
never advances — a hung socket outlived the 120s window entirely. The loop now
also carries a wall-clock deadline and caps each request with
`AbortSignal.timeout(remaining)`, so a stalled connection is aborted at the
window rather than after it. An abort surfaces as the window message; any other
transport failure is rethrown unchanged.

A transient response late in the window added the prospective backoff to
`elapsed` and broke before sleeping it, ending polling up to 15s early and
abandoning a job that was still running. The backoff is now clamped to the
remaining budget and always slept, so the loop waits out the full window.

202 and to-be-retried responses had their bodies abandoned mid-stream, holding
the socket out of the pool for the rest of the poll. Both paths now cancel the
body before continuing.

Tests: the Retry-After case now asserts the delay it claims to prove rather
than only that a 429 is retried — dropping `readRetryAfterMs` from the backoff
call turns it red. Added coverage for body release, the per-request abort
signal, abort-to-window-message, transport-error passthrough, and the
full-window wait.

Also splits the finder and verifier doc citations on ENROW_CREDIT_USD; the
0.25-credit claim was hanging off the finder endpoint. The rate itself is
unchanged.

Copy link
Copy Markdown
Collaborator Author

@greptile

Pushed 7a201a8 addressing all six threads; each has an individual reply and is resolved.

  • poll.ts:63 (P1) — the budget was accounted only in intended sleep time, which await fetch never advances. Added a wall-clock deadline and AbortSignal.timeout(remaining) per request. Pushed back on composing a caller signal: ToolConfig.postProcess is (result, params, executeTool) and no AbortSignal reaches it, so that parameter would be dead today.
  • poll.ts:77 (P1/P2) — backoff is clamped to the remaining budget and always slept, instead of breaking on projected elapsed. No more stopping up to 15s early.
  • poll.ts:67 (P2) — 202 and retried responses now cancel their body before continuing.
  • find_email.test.ts:164 (P2) — asserted the delay rather than renaming the test; mutation-tested by dropping readRetryAfterMs, which turns it red.
  • hosting.ts:13 (P3) — finder and verifier citations split. ENROW_CREDIT_USD is deliberately unchanged; reasoning is on that thread.

lint, check:audits (39/39), check-block-registry, type-check and the 30 scoped tests are all green.

Comment thread apps/sim/tools/enrow/poll.ts Outdated
The backoff was clamped against `MAX_POLL_TIME_MS - elapsed`, but `elapsed`
charges nothing for time spent inside `await fetch`. With slow polls the real
clock runs ahead of it, so that term overstates the remainder and a wait sized
against it lands past the deadline — up to 15s past, on a capped Retry-After.
The poll interval had the same defect at a smaller scale, worth up to 3s.

Both waits now go through one `remainingBudgetMs()` helper returning whichever
of the two budgets has less left, so there is a single clamp rule rather than
two that disagree. The request timeout keeps using the wall clock directly,
which is the only bound that means anything to an in-flight socket.

The new test drives a stubbed clock that the mocked `sleep` and the `fetch`
stub both advance, so the second poll completes past the deadline while
`elapsed` still believes 99s remain. It asserts the backoff is dropped rather
than clamped to that phantom budget, and that no sleep begins after the
deadline. Reverting the clamp to `MAX_POLL_TIME_MS - elapsed` turns it red.

Copy link
Copy Markdown
Collaborator Author

@greptile

Pushed 37b8101. The P1 on the backoff clamp was correct and is fixed — elapsed charges nothing for time inside await fetch, so it overstated the remainder whenever polls were slow.

Went slightly wider than the suggested three-term Math.min: the poll interval had the same defect at a smaller scale (up to 3s), so both waits now share one remainingBudgetMs() helper rather than leaving two clamp rules that disagree. Reply on the thread has the detail and the mutation test that pins it.

All 7 threads across both rounds are replied to and resolved. Gates green: lint, check:audits 39/39, check-block-registry, type-check, 31 scoped tests.

greptile-apps Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Thanks for the follow-up. The P1 backoff-budget issue is addressed, and factoring both the retry backoff and poll interval through remainingBudgetMs() is the right broader fix—the waits now consistently account for time spent in slow fetches without extending the 120s cap. The mutation test and green gates cover the correction. No further issues from me.

Copy link
Copy Markdown
Collaborator Author

@greptile review

Requesting a full re-review so the summary reflects 37b8101323. The scored summary above was last refreshed at 04:27:40Z against 7a201a8b11, and its "Files Needing Attention: apps/sim/tools/enrow/poll.ts" finding — retry backoff not bounded by the post-fetch wall-clock remainder — was fixed in 37b8101323, which landed afterwards.

Both waits now route through a single remainingBudgetMs() returning Math.max(0, Math.min(MAX_POLL_TIME_MS - elapsed, deadline - Date.now())), so the backoff and the poll interval are both bounded by the real remainder. The summary also notes the late-backoff test "does not model time spent awaiting fetch" — that gap is closed by the new never sleeps past the wall-clock deadline when the polls themselves are slow test, which stubs Date.now and advances it from both the mocked sleep and the fetch stub so a poll costs 55s of wall clock against 3s of intended delay.

All 7 threads across both rounds are replied to and resolved.

The comment justifying `requestsPerMinute: 60` claimed "Enrow rate limit
is ~50 req/s". Enrow documents 10 req/s per API key on every POST
endpoint (https://docs.enrow.io/rate-limits) — 600/min, not 3,000.

The cap itself stays correct: 60/min is conservative against either
figure, and the reason for it (not bursting into the limit while a job
polls) is unchanged. Only the number a future reader would size the cap
against was wrong — the same false-citation class this branch already
fixes on ENROW_CREDIT_USD.

Copy link
Copy Markdown
Collaborator Author

@greptile review

Copy link
Copy Markdown
Collaborator Author

@cubic review

cubic-dev-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

@cubic review

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

cubic-dev-ai 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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

1 issue found across 7 files

Confidence score: 4/5

  • In apps/sim/tools/enrow/poll.ts, isTransientStatus treats statuses such as 600 as transient and retries them, which can delay failure handling or cause unnecessary polling; restrict the check to the 500–599 range.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/sim/tools/enrow/poll.ts">

<violation number="1" location="apps/sim/tools/enrow/poll.ts:25">
P2: When the poll endpoint or an intermediary returns a status outside the 5xx range, such as 600, `isTransientStatus` retries it because it checks only `>= 500`. Restrict the range to 500–599 so only 5xx responses and 429 are retried.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/tools/enrow/poll.ts Outdated
`isTransientStatus` checked `status >= 500`, so a 6xx would have been
retried as if it were a server error. A status is a three-digit field:
the `Response` constructor refuses anything outside 200-599, but a status
parsed off the wire is not built that way, so a misbehaving intermediary
can surface one. A 6xx is not something a later poll recovers from, so it
now fails fast with the status attached rather than burning the poll's
retry budget on it.

Test asserts a single call for a 600; verified red against the old
predicate.

Copy link
Copy Markdown
Collaborator Author

@greptile review

Copy link
Copy Markdown
Collaborator Author

@cubic review

cubic-dev-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

@cubic review

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

cubic-dev-ai 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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

2 issues found across 7 files

Confidence score: 3/5

  • apps/sim/tools/enrow/hosting.ts does not rate-limit repeated polling requests, so concurrent Enrow calls can exceed the provider’s 60/minute limit and create request bursts — apply the limiter to each poll request.
  • apps/sim/blocks/blocks/sixtyfour.ts can emit struct: null for an untouched required subBlock, causing the mapper to send an invalid required parameter; treat null and cleared empty strings as absent in both struct paths.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/sim/tools/enrow/hosting.ts">

<violation number="1" location="apps/sim/tools/enrow/hosting.ts:48">
P2: During concurrent Enrow calls, this setting does not cap provider requests at 60/min or prevent polling bursts: the limiter counts only initial tool executions, not the repeated poll requests. Apply the limiter to each provider request or document this as a per-workspace execution cap rather than a provider-request safeguard.</violation>
</file>

<file name="apps/sim/blocks/blocks/sixtyfour.ts">

<violation number="1" location="apps/sim/blocks/blocks/sixtyfour.ts:242">
P2: When an untouched required struct subBlock resolves to `null`, this guard still writes `struct: null`, so the mapper sends an invalid required parameter. Treat `null` (and cleared empty strings) as absent in both struct mappings.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/tools/enrow/hosting.ts Outdated
Comment thread apps/sim/blocks/blocks/sixtyfour.ts Outdated
The guard added earlier tested `!== undefined`, which is not enough. The
serializer stores every untouched subBlock as `params[id] ?? null`, so an
unfilled field arrives as `null` and a cleared one as `''` — both pass
`!== undefined` and get written straight over the model's tool-call
argument. `struct: null` reaches the tool as an invalid required param;
`struct: ''` fails its `JSON.parse` with "struct must be valid JSON".

Replaced with a `present()` helper testing all three, which is the same
guard `trigger_dev`'s `scoped()` and the `enrow` mapper already use.
Applied to the switches too, so `Boolean(null)` can no longer force
`false` over a `true` the model sent, while a switch the user actually
turned off still forwards as `false`.

Also corrects what the hosted-key rate limit claims to do. The limiter is
consulted once per tool execution, not per outbound request, so it never
throttled the poll loop's GETs and the old comment's "avoid bursting into
the limit during polling" was wrong. It is accurate as a per-workspace
execution cap: each execution issues exactly one POST, and Enrow's
documented 10 req/s applies to POST endpoints only, so 60/min sits an
order of magnitude under the ceiling.

Four tests, each verified red against the weaker guard.

Copy link
Copy Markdown
Collaborator Author

@greptile review

Copy link
Copy Markdown
Collaborator Author

@cubic review

cubic-dev-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

@cubic review

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

Comment thread apps/sim/tools/enrow/poll.ts Outdated

cubic-dev-ai 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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

1 issue found across 7 files

Confidence score: 3/5

  • apps/sim/tools/enrow/poll.ts: If headers arrive before the timeout but the response body stalls, response.json() can reject outside the current abort handling, allowing the post-processor to return an unintended result; extend timeout/abort handling through body parsing and cover the stalled-body case.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/sim/tools/enrow/poll.ts">

<violation number="1" location="apps/sim/tools/enrow/poll.ts:149">
P1: Handle timeout aborts while reading the response body. When headers arrive before the deadline but the body does not, `response.json()` rejects outside the current abort handler, allowing the post-processor to return the original result with null fields instead of reporting a polling timeout.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/tools/enrow/poll.ts Outdated
`AbortSignal.timeout` fires against the whole exchange, so a 200 whose
headers arrive just inside the window can still have its body aborted
mid-read. That rejection surfaces at `response.json()`, outside the
try/catch guarding the `fetch`, so the raw `TimeoutError` escaped —
naming neither Enrow nor the window it exhausted. It now takes the same
abort path and ends the poll with this module's own window error.

The error-body read gets the same treatment from the other direction: a
body that cannot be read no longer replaces a terminal failure with an
unrelated transport error. The status is the diagnostic and it now
survives, with `<unreadable body>` standing in for the text.

Both tests verified red against the unguarded reads.

Copy link
Copy Markdown
Collaborator Author

@greptile review

Copy link
Copy Markdown
Collaborator Author

@cubic review

cubic-dev-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

@cubic review

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

cubic-dev-ai 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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

2 issues found across 7 files

Confidence score: 3/5

  • In apps/sim/tools/enrow/poll.ts, a timeout while reading a 200 response can restore the original successful submission result, returning success: true with incomplete null fields; handle the polling timeout so incomplete results are not reported as successful.
  • In apps/sim/tools/enrow/hosting.ts, 429/503 retries can re-acquire a key and issue up to three hosted POSTs for one execution, so the comment should distinguish the 60-executions/min admission limit from retry behavior.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/sim/tools/enrow/hosting.ts">

<violation number="1" location="apps/sim/tools/enrow/hosting.ts:50">
P3: When Enrow returns 429/503, `executeTool` retries the hosted POST up to three times and may re-acquire a key, so one execution can issue multiple POSTs. Update this comment to distinguish the 60 executions/min admission cap from actual provider request volume.</violation>
</file>

<file name="apps/sim/tools/enrow/poll.ts">

<violation number="1" location="apps/sim/tools/enrow/poll.ts:125">
P1: When the 200 response body times out, `pollEnrowJob` throws and the executor restores the original successful submission result, so the tool returns `success: true` with incomplete null fields. Catch the polling-window error in each Enrow `postProcess` and return a failed tool response instead of allowing the exception to escape.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

A throw out of `postProcess` never reaches the user. `executeTool` wraps
every `postProcess` call in a catch that logs and then restores the
pre-`postProcess` result — which for these tools is the *submit*
response: `success: true` with every result field null. So a poll that
timed out, exhausted its retries, or hit a terminal status was reported
as a successful lookup that simply found nothing.

Both Enrow tools now catch the poll failure and return it as
`success: false` with the message and the job id preserved. Returning
rather than throwing also stops the hosted-key cost hook, which is gated
on `finalResult.success`, from billing an execution that produced no
result.

The eleven existing failure-path tests asserted the throwing contract and
were bypassing the executor wrapper, which is exactly why this went
unnoticed; they now assert the returned failure. One test pins the whole
shape — `success: false`, the error, and the null-field output — so the
silent-success regression cannot come back.

Also corrects the rate-limit comment again: `executeTool` retries an
upstream 429/503 and can re-acquire a key, so an admitted execution can
issue more than one POST. The 60/min figure is an admission cap, and even
at that retry ceiling it stays an order of magnitude under Enrow's
documented 600/min.

Copy link
Copy Markdown
Collaborator Author

@greptile review

Copy link
Copy Markdown
Collaborator Author

@cubic review

cubic-dev-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

@cubic review

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

cubic-dev-ai 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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

No issues found across 7 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

Copy link
Copy Markdown
Collaborator Author

Closing for now — not because of a defect. This batch grew to 17 PRs across ~700 changed call sites, and we would rather revisit it as smaller, independently testable pieces than merge this much at once.

Nothing here is lost: the branch fix/sixtyfour-enrow-integrity is preserved and this PR can be reopened. Review state, the reasoning on every thread, and the red-first verification all stay attached.

waleedlatif1 deleted the fix/sixtyfour-enrow-integrity branch August 29, 2026 07:16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant


Back | FazBrowse Home | New Git URL