Skip to content

fix: parallelize thread PR fetches and increase Slack API timeout#335

Merged
sweetmantech merged 5 commits intotestfrom
hotfix/slack-timeout
Mar 24, 2026
Merged

fix: parallelize thread PR fetches and increase Slack API timeout#335
sweetmantech merged 5 commits intotestfrom
hotfix/slack-timeout

Conversation

@sweetmantech
Copy link
Copy Markdown
Contributor

@sweetmantech sweetmantech commented Mar 24, 2026

Summary

  • Parallelize thread PR fetches in batches of 10 (was sequential, causing timeouts with 30+ tags)
  • Increase per-request Slack API timeout from 10s to 30s

Problem

GET /api/admins/coding/slack returns 500 for period=weekly and longer — each tag sequentially calls conversations.replies, timing out the serverless function.

Test plan

  • Verify period=weekly and period=all return 200 on preview deployment
  • Verify period=daily still works

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Performance Improvements

    • Optimized Slack mention retrieval through batch processing of pull request data for faster performance.
  • Bug Fixes

    • Removed timeout constraint on Slack API requests to prevent unexpected failures.

Sequential thread fetches caused timeouts with 30+ tags.
Now fetches in parallel batches of 10 and increases per-request
timeout from 10s to 30s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 24, 2026

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

Project Deployment Actions Updated (UTC)
recoup-api Ready Ready Preview Mar 24, 2026 3:08am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

Warning

Rate limit exceeded

@sweetmantech has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 23 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d5779f0d-247f-4605-a127-056bc426aee9

📥 Commits

Reviewing files that changed from the base of the PR and between 21cbacb and deca112.

⛔ Files ignored due to path filters (1)
  • lib/slack/__tests__/slackGet.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (2)
  • lib/admins/slack/fetchAllThreadPullRequests.ts
  • lib/slack/slackGet.ts
📝 Walkthrough

Walkthrough

This pull request refactors the Slack mention extraction pipeline by introducing a batching architecture: new buildSlackTags constructs enriched tag objects, new fetchAllThreadPullRequests batches PR fetches across threads in parallel, and fetchSlackMentions is reorganized to collect raw mentions first, then perform a single batch PR fetch, and finally delegate tag construction. Additionally, the 10-second timeout constraint is removed from the Slack API GET handler.

Changes

Cohort / File(s) Summary
New Slack Tag Building & Batching
lib/admins/slack/buildSlackTags.ts, lib/admins/slack/fetchAllThreadPullRequests.ts
New modules introducing a two-stage pipeline: fetchAllThreadPullRequests batches PR extraction across multiple threads in parallel (10 at a time), while buildSlackTags enriches raw mentions with user cache data and computed timestamps, then sorts by recency.
Slack Mention Fetching Refactor
lib/admins/slack/fetchSlackMentions.ts
Refactored to accumulate lightweight RawMention objects first, delegate per-thread PR fetching to the new fetchAllThreadPullRequests batch function, and hand off tag construction to buildSlackTags, eliminating inline per-mention thread processing.
Slack API Client
lib/slack/slackGet.ts
Removed hardcoded 10-second abort timeout from the fetch call, allowing requests to complete without artificial time constraints.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant fetchSlackMentions
    participant Slack API
    participant fetchAllThreadPullRequests
    participant buildSlackTags
    participant Cache as User Cache

    Client->>fetchSlackMentions: Fetch Slack mentions (period)
    fetchSlackMentions->>Slack API: Search for user mentions in period
    Slack API-->>fetchSlackMentions: Raw mention records
    
    fetchSlackMentions->>fetchAllThreadPullRequests: Batch fetch PRs from threads
    loop Parallel batches (10 at a time)
        fetchAllThreadPullRequests->>Slack API: Get thread PRs (channelId, ts)
        Slack API-->>fetchAllThreadPullRequests: PR URLs
    end
    fetchAllThreadPullRequests-->>fetchSlackMentions: All PR results (string[][])
    
    fetchSlackMentions->>Cache: Lookup user details
    Cache-->>fetchSlackMentions: Names & avatars
    
    fetchSlackMentions->>buildSlackTags: Build tags (mentions, userCache, PRs)
    buildSlackTags->>buildSlackTags: Enrich mentions + compute timestamps
    buildSlackTags->>buildSlackTags: Sort by timestamp (descending)
    buildSlackTags-->>fetchSlackMentions: SlackTag[]
    
    fetchSlackMentions-->>Client: SlackTag[] (enriched, sorted)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

Mentions once lingered, one thread at a time,
Now batching brings harmony—ten dance in rhyme,
Raw tags bloom enriched from the cache's sweet store,
Sorted by freshness, timeouts freed evermore. ✨

🚥 Pre-merge checks | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning Pull request introduces three critical issues: unsafe array initialization with shared references, missing null checks on cache access, and unhandled Promise.all rejections. Use Array.from() for safe initialization, add null coalescing checks, and replace Promise.all with Promise.allSettled for resilient error handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hotfix/slack-timeout

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 and usage tips.

Collect mentions in phase 1, fetch thread PRs in parallel in phase 2,
build final SlackTag array in phase 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
const res = await fetch(url.toString(), {
headers: { Authorization: `Bearer ${token}` },
signal: AbortSignal.timeout(10_000),
signal: AbortSignal.timeout(30_000),
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.

Remove this signal altogether.

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.

Resolved.

Comment on lines +100 to +117
// Fetch thread PRs in parallel (batches of 10 to avoid rate limits)
const BATCH_SIZE = 10;
for (let i = 0; i < tags.length; i += BATCH_SIZE) {
const batch = tags.slice(i, i + BATCH_SIZE);
const results = await Promise.all(
batch.map(tag =>
fetchThreadPullRequests(token, tag.channel_id, (tag as { _threadTs: string })._threadTs),
),
);
for (let j = 0; j < batch.length; j++) {
batch[j].pull_requests = results[j];
}
}

// Clean up internal field and sort
for (const tag of tags) {
delete (tag as Record<string, unknown>)._threadTs;
}
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.

Open closed principle

  • move new code to a new lib file.

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.

Resolved.

Comment on lines +101 to +127
const BATCH_SIZE = 10;
const allPullRequests: string[][] = new Array(mentions.length).fill([]);

for (let i = 0; i < mentions.length; i += BATCH_SIZE) {
const batch = mentions.slice(i, i + BATCH_SIZE);
const results = await Promise.all(
batch.map(m => fetchThreadPullRequests(token, m.channelId, m.ts)),
);
for (let j = 0; j < batch.length; j++) {
allPullRequests[i + j] = results[j];
}
}

// Phase 3: Build final tags
const tags: SlackTag[] = mentions.map((m, i) => {
const { name, avatar } = userCache[m.userId];
return {
user_id: m.userId,
user_name: name,
user_avatar: avatar,
prompt: m.prompt,
timestamp: new Date(parseFloat(m.ts) * 1000).toISOString(),
channel_id: m.channelId,
channel_name: m.channelName,
pull_requests: allPullRequests[i],
};
});
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.

OCP - new lib for new code.

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.

Resolved.

- Remove signal timeout from slackGet
- Extract parallel batch fetching to fetchAllThreadPullRequests.ts
- Extract tag building to buildSlackTags.ts
- fetchSlackMentions now only collects raw mentions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sweetmantech
Copy link
Copy Markdown
Contributor Author

Endpoint Testing — Preview Deployment

Preview URL: https://recoup-api-git-hotfix-slack-timeout-recoupable-ad724970.vercel.app

Results

Period Status Total Tags with PRs Total PRs Response Time
daily ✅ 200 9 2.8s
weekly ✅ 200 35 23 27 2.6s
monthly ✅ 200 131 82 119 3.1s
all ❌ 500 4.4s (timeout)

Analysis

  • daily, weekly, and monthly all return successfully — the parallel batching fix works
  • all still times out — with 131+ tags and the channel history pagination, the total Slack API calls exceed the serverless function timeout
  • Previously weekly and monthly also returned 500 — now both work

Recommendation

The all period may need server-side pagination or caching to handle the full history. For now, monthly covers the practical use case.

🤖 Generated with Claude Code

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (6)
lib/admins/slack/fetchSlackMentions.ts (2)

89-89: Static analysis: ReDoS warning on dynamic regex.

The regex new RegExp(\<@${botUserId}>\s*`, "g")uses variable input. WhilebotUserIdis controlled (sourced from Slack'sauth.testAPI and follows the formatU[A-Z0-9]+`), static analyzers flag this pattern.

The risk here is low since botUserId contains only alphanumeric characters. If you want to silence the warning and be defensive, escape the ID.

🛡️ Optional: escape botUserId for safety
+function escapeRegex(str: string): string {
+  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+}
+
 // In the loop:
-prompt: (msg.text ?? "").replace(new RegExp(`<@${botUserId}>\\s*`, "g"), "").trim(),
+prompt: (msg.text ?? "").replace(new RegExp(`<@${escapeRegex(botUserId)}>\\s*`, "g"), "").trim(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/admins/slack/fetchSlackMentions.ts` at line 89, The dynamic RegExp
construction in the prompt assignment (the expression using new
RegExp(`<@${botUserId}>\\s*`, "g")) triggers a ReDoS warning; update the code in
fetchSlackMentions.ts to defensively escape botUserId before building the RegExp
(or avoid RegExp by using a safe string-based replace) so only alphanumeric
characters are treated literally; ensure you reference the same expression (the
prompt: ... replace/new RegExp usage) and replace it with an escaped-pattern or
non-RegExp approach to silence the static analyzer.

34-40: Duplicate RawMention interface.

This interface is also defined in buildSlackTags.ts. Extract to a shared types file to comply with DRY.

As per coding guidelines: "Extract shared logic into reusable utilities following Don't Repeat Yourself (DRY) principle".

♻️ Proposed extraction

Create lib/admins/slack/types.ts:

export interface RawMention {
  userId: string;
  prompt: string;
  ts: string;
  channelId: string;
  channelName: string;
}

Then import in both files:

import type { RawMention } from "./types";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/admins/slack/fetchSlackMentions.ts` around lines 34 - 40, Duplicate
RawMention interface is defined in two places; extract it into a single exported
type in a new module (e.g., lib/admins/slack/types.ts) and replace the local
definitions with an import: export interface RawMention { userId: string;
prompt: string; ts: string; channelId: string; channelName: string; } then in
the files that used the duplicate (the modules defining/building Slack tags and
fetchSlackMentions) remove the local RawMention declaration and add import type
{ RawMention } from "./types"; so both modules reference the single shared
RawMention type.
lib/admins/slack/buildSlackTags.ts (3)

30-30: Assumption of index alignment between mentions and pullRequests.

The function relies on mentions and pullRequests having matching lengths and order. While the caller (fetchSlackMentions) currently ensures this, adding a guard or assertion would make the contract explicit and catch misuse early.

🛡️ Optional defensive assertion
 export function buildSlackTags(
   mentions: RawMention[],
   userCache: Record<string, { name: string; avatar: string | null }>,
   pullRequests: string[][],
 ): SlackTag[] {
+  if (mentions.length !== pullRequests.length) {
+    throw new Error("mentions and pullRequests arrays must have matching lengths");
+  }
   const tags: SlackTag[] = mentions.map((m, i) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/admins/slack/buildSlackTags.ts` at line 30, The loop in buildSlackTags
assumes mentions and pullRequests are aligned; add an explicit guard at the
start of buildSlackTags: check that mentions.length === pullRequests.length (or
throw an Error/return early with a descriptive log via processLogger) so
mismatched input is caught immediately; reference the mentions and pullRequests
parameters and the loop that uses pull_requests: pullRequests[i] to locate where
to add the assertion/guard.

3-9: Consider extracting RawMention interface to a shared types file.

This interface is duplicated in fetchSlackMentions.ts (lines 34-40). To adhere to the DRY principle, extract it to a shared location (e.g., lib/admins/slack/types.ts) and import it in both files.

As per coding guidelines: "Extract shared logic into reusable utilities following Don't Repeat Yourself (DRY) principle".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/admins/slack/buildSlackTags.ts` around lines 3 - 9, The RawMention
interface is duplicated; extract it into a shared types module (e.g., create a
new types file exporting RawMention) and replace the inline declarations in
buildSlackTags.ts and fetchSlackMentions.ts with an import of that shared type.
Ensure the exported name is RawMention, update both files to import { RawMention
} from the new module, and run TypeScript type checks to confirm no other
references need adjustment.

34-34: Sorting can be simplified using localeCompare for ISO strings.

Since timestamps are ISO 8601 strings, they sort lexicographically. This avoids redundant Date parsing.

♻️ Simpler sort
-  tags.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
+  tags.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/admins/slack/buildSlackTags.ts` at line 34, The sort comparator in
buildSlackTags.ts currently parses Dates on every comparison (tags.sort(...))
even though tag.timestamp values are ISO 8601 strings; change the comparator to
use string comparison via localeCompare on the timestamp property to perform a
lexicographic descending sort (compare b.timestamp to a.timestamp) instead of
creating new Date objects.
lib/admins/slack/fetchAllThreadPullRequests.ts (1)

19-19: Consider making BATCH_SIZE configurable or documenting the choice.

The batch size of 10 is a reasonable default, but documenting why this value was chosen (e.g., Slack rate limits, serverless timeout constraints) would help future maintainers. Alternatively, expose it as an optional parameter for flexibility.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/admins/slack/fetchAllThreadPullRequests.ts` at line 19, The constant
BATCH_SIZE (currently set to 10) in fetchAllThreadPullRequests should be made
configurable or documented: either expose it as an optional parameter on the
fetchAllThreadPullRequests function (or accept an options object) so callers can
override the batch size, or add a clear comment above the BATCH_SIZE declaration
explaining why 10 was chosen (e.g., Slack rate limits, serverless timeout,
memory). Update any call sites to pass the new parameter where needed and
include a short note in surrounding docs/comments describing the rationale and
recommended limits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/admins/slack/buildSlackTags.ts`:
- Around line 20-21: The destructuring in the mentions.map callback (const {
name, avatar } = userCache[m.userId]) can throw if userCache[m.userId] is
undefined; update the mentions.map in buildSlackTags to defensively handle
missing cache entries by checking for existence of userCache[m.userId] (or using
a default/fallback object) before destructuring and provide sensible fallbacks
for name and avatar when absent so SlackTag creation never throws.

In `@lib/admins/slack/fetchAllThreadPullRequests.ts`:
- Line 20: The results array is initialized with a single shared empty array via
new Array(threads.length).fill([]), which causes all slots to reference the same
array; change the initialization in fetchAllThreadPullRequests (the results
variable) to create distinct sub-arrays for each thread — e.g., use Array.from({
length: threads.length }, () => []) or map over threads to produce a new [] per
index, or simply pre-size results and assign into results[i] in the loop without
fill — ensuring each results[i] is an independent array.
- Around line 22-30: The current batch logic uses Promise.all which aborts the
whole batch if any fetchThreadPullRequests rejects; change it to use
Promise.allSettled (or wrap each fetchThreadPullRequests call with a try/catch)
so individual failures don't reject the batch — for any rejected promise return
an empty array (or a consistent failure placeholder) and still assign results[i
+ j] = batchResult for each item; update the block that constructs batchResults
(the map over batch calling fetchThreadPullRequests) and the subsequent loop
that writes into results to handle settled results (checking status ===
"fulfilled" vs "rejected" and using value or fallback).

---

Nitpick comments:
In `@lib/admins/slack/buildSlackTags.ts`:
- Line 30: The loop in buildSlackTags assumes mentions and pullRequests are
aligned; add an explicit guard at the start of buildSlackTags: check that
mentions.length === pullRequests.length (or throw an Error/return early with a
descriptive log via processLogger) so mismatched input is caught immediately;
reference the mentions and pullRequests parameters and the loop that uses
pull_requests: pullRequests[i] to locate where to add the assertion/guard.
- Around line 3-9: The RawMention interface is duplicated; extract it into a
shared types module (e.g., create a new types file exporting RawMention) and
replace the inline declarations in buildSlackTags.ts and fetchSlackMentions.ts
with an import of that shared type. Ensure the exported name is RawMention,
update both files to import { RawMention } from the new module, and run
TypeScript type checks to confirm no other references need adjustment.
- Line 34: The sort comparator in buildSlackTags.ts currently parses Dates on
every comparison (tags.sort(...)) even though tag.timestamp values are ISO 8601
strings; change the comparator to use string comparison via localeCompare on the
timestamp property to perform a lexicographic descending sort (compare
b.timestamp to a.timestamp) instead of creating new Date objects.

In `@lib/admins/slack/fetchAllThreadPullRequests.ts`:
- Line 19: The constant BATCH_SIZE (currently set to 10) in
fetchAllThreadPullRequests should be made configurable or documented: either
expose it as an optional parameter on the fetchAllThreadPullRequests function
(or accept an options object) so callers can override the batch size, or add a
clear comment above the BATCH_SIZE declaration explaining why 10 was chosen
(e.g., Slack rate limits, serverless timeout, memory). Update any call sites to
pass the new parameter where needed and include a short note in surrounding
docs/comments describing the rationale and recommended limits.

In `@lib/admins/slack/fetchSlackMentions.ts`:
- Line 89: The dynamic RegExp construction in the prompt assignment (the
expression using new RegExp(`<@${botUserId}>\\s*`, "g")) triggers a ReDoS
warning; update the code in fetchSlackMentions.ts to defensively escape
botUserId before building the RegExp (or avoid RegExp by using a safe
string-based replace) so only alphanumeric characters are treated literally;
ensure you reference the same expression (the prompt: ... replace/new RegExp
usage) and replace it with an escaped-pattern or non-RegExp approach to silence
the static analyzer.
- Around line 34-40: Duplicate RawMention interface is defined in two places;
extract it into a single exported type in a new module (e.g.,
lib/admins/slack/types.ts) and replace the local definitions with an import:
export interface RawMention { userId: string; prompt: string; ts: string;
channelId: string; channelName: string; } then in the files that used the
duplicate (the modules defining/building Slack tags and fetchSlackMentions)
remove the local RawMention declaration and add import type { RawMention } from
"./types"; so both modules reference the single shared RawMention type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7cce05d3-ee9b-4b1d-b5c9-d5ebc4724463

📥 Commits

Reviewing files that changed from the base of the PR and between e4735c0 and 21cbacb.

📒 Files selected for processing (4)
  • lib/admins/slack/buildSlackTags.ts
  • lib/admins/slack/fetchAllThreadPullRequests.ts
  • lib/admins/slack/fetchSlackMentions.ts
  • lib/slack/slackGet.ts
💤 Files with no reviewable changes (1)
  • lib/slack/slackGet.ts

Comment on lines +20 to +21
const tags: SlackTag[] = mentions.map((m, i) => {
const { name, avatar } = userCache[m.userId];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential runtime error if user is missing from cache.

The destructuring const { name, avatar } = userCache[m.userId] will throw a TypeError if m.userId is not a key in userCache. This could occur if upstream logic fails to populate the cache for a user.

🛡️ Proposed defensive fix
   const tags: SlackTag[] = mentions.map((m, i) => {
-    const { name, avatar } = userCache[m.userId];
+    const cached = userCache[m.userId] ?? { name: "Unknown", avatar: null };
+    const { name, avatar } = cached;
     return {
📝 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
const tags: SlackTag[] = mentions.map((m, i) => {
const { name, avatar } = userCache[m.userId];
const tags: SlackTag[] = mentions.map((m, i) => {
const cached = userCache[m.userId] ?? { name: "Unknown", avatar: null };
const { name, avatar } = cached;
return {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/admins/slack/buildSlackTags.ts` around lines 20 - 21, The destructuring
in the mentions.map callback (const { name, avatar } = userCache[m.userId]) can
throw if userCache[m.userId] is undefined; update the mentions.map in
buildSlackTags to defensively handle missing cache entries by checking for
existence of userCache[m.userId] (or using a default/fallback object) before
destructuring and provide sensible fallbacks for name and avatar when absent so
SlackTag creation never throws.

- slackGet retries once on 429 using Retry-After header
- Reduce parallel batch size from 10 to 5
- Add 1.1s delay between batches to respect Slack rate limits
- Add tests for 429 retry behavior

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ilience

- Array.from creates unique arrays per slot (fill([]) shared one reference)
- Promise.allSettled prevents one failed thread from crashing the batch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sweetmantech sweetmantech merged commit 8306a67 into test Mar 24, 2026
4 checks passed
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