Skip to content

test: add renderer stress explorer#172

Merged
kjgbot merged 1 commit into
mainfrom
claude/stress-exploration
Jun 9, 2026
Merged

test: add renderer stress explorer#172
kjgbot merged 1 commit into
mainfrom
claude/stress-exploration

Conversation

@miyaontherelay

Copy link
Copy Markdown
Contributor

Summary

  • add parameterized Playwright stress explorer for renderer broker-load exploration
  • include JSONL result capture and non-gating PASS/FAIL recording for FPS/console-error outcomes
  • document completed agent-count, event-rate, and chat-mix ramp results in STRESS-EXPLORATION-REPORT.md

Key findings so far

  • PTY-heavy agent-count knee is around 1.6k-1.75k agents at 10 events/sec/agent
  • fixed 1k agents stays smooth up to 250 events/sec/agent, so raw PTY line volume is not the first bottleneck
  • chat mix is borderline at 1% and fails consistently at 2.5%+

Testing

  • npm run build:web
  • npx playwright test tests/playwright/stress-explorer.spec.ts --config playwright.stress.config.ts --list
  • multiple stress-explorer ramp runs captured in tests/playwright/STRESS-EXPLORATION-REPORT.md

Note: this branch is stacked on origin/claude/web-stress-harness because main does not yet contain the calibrated stress harness baseline.

@codeant-ai

codeant-ai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kjgbot, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 52 minutes and 46 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7fb2eca1-6100-4cf6-bae0-8a2001dead8b

📥 Commits

Reviewing files that changed from the base of the PR and between 54fb4bb and 16cd553.

📒 Files selected for processing (2)
  • tests/playwright/STRESS-EXPLORATION-REPORT.md
  • tests/playwright/stress-explorer.spec.ts
📝 Walkthrough

Walkthrough

This PR adds comprehensive stress testing infrastructure to the renderer. It introduces an abstracted and mockable IPC layer, a full in-memory PearAPI mock with test harness controls, web build and Playwright configuration, and two Playwright stress test suites that measure renderer performance under synthetic 1000-agent broker load with variable event rates and chat patterns.

Changes

Renderer Stress Testing

Layer / File(s) Summary
IPC Abstraction Layer
src/renderer/src/lib/ipc-electron.ts, src/renderer/src/lib/ipc.ts, src/renderer/src/vite-env.d.ts, src/renderer/src/hooks/use-message-reconciliation.ts
Introduces typed window.pear declaration and an environment-controlled pear export that selects between Electron and mock implementations; updates reconciliation hook to import and use pear rather than window.pear.
Mock IPC Implementation
src/renderer/src/lib/ipc-mock.ts
Provides complete in-memory pearMock API with broker, project, agent, terminal, message, integration, updater, git/fs/auth surfaces; maintains agents/events/messages/PTY chunks state and exposes pearMockHarness for test control (reset, inject events/chunks, spawn agents, open channel/agents, getState).
Web Build Configuration
vite.web.config.ts, tsconfig.web.json
Adds Vite web build config with VITE_PEAR_MOCK_IPC forced to true, path aliases, React/Tailwind plugins, output to out/web, and updates TypeScript target/libs to ES2022.
Test Framework Setup
package.json, playwright.stress.config.ts, .gitignore
Adds build:web and test:stress npm scripts, creates playwright.stress.config.ts for stress runs (Vite preview, device/use settings, timeouts, reporter), and ignores Playwright stress artifacts in .gitignore.
Component Test Instrumentation
src/renderer/src/components/chat/ChatMessage.tsx, src/renderer/src/components/terminal/TerminalInstance.tsx, src/renderer/src/components/common/StatusBar.tsx, src/renderer/src/components/diff/DiffPane.tsx, src/renderer/src/components/graph/GraphView.tsx, src/renderer/src/components/settings/AccountSettings.tsx, src/renderer/src/lib/syntax-highlighter.tsx
Adds data-testid/data-message-id/data-message-kind to chat messages, data-testid/data-agent-name to terminal instances; narrows StatusBar polling deps; casts DiffPane root to ProjectRoot; refactors GraphView edge construction; guards AccountSettings integration event handling; adjusts syntax-highlighter casts.
Stress Test Specifications
tests/playwright/stress-1000-agents.spec.ts, tests/playwright/stress-explorer.spec.ts, tests/playwright/STRESS-EXPLORATION-REPORT.md
Adds a fixed 1000-agent stress spec and a parameterized explorer spec that generate synthetic broker/PTY/chat load, sample frame timing and heap metrics, validate UI reconciliation and terminal output, persist JSONL results, and documents exploration findings and recommendations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 With mock in place, the stress tests run,
A thousand agents under the sun,
Frame rates measured, messages dance,
DOM IDs validated at a glance,
The renderer survives the chaotic test— ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'test: add renderer stress explorer' directly and clearly summarizes the main change: adding a new stress testing tool (stress explorer) for the renderer.
Description check ✅ Passed The description is directly related to the changeset, providing a summary of the stress explorer addition, key findings from testing, and usage instructions that all align with the changes made.
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.

✏️ 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 claude/stress-exploration

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.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a Playwright-based stress testing harness for the renderer, including configuration files, mock IPC implementations (ipc-mock.ts and ipc-electron.ts), and stress test specs to simulate high agent and chat loads. It also includes minor bug fixes, type safety improvements, and DOM test attributes. The review feedback highlights a rate-generation error in the stress explorer's fractional chat distribution, a potential runtime TypeError in AccountSettings.tsx when checking properties on a potentially null event, and an anti-pattern of casting types to never in syntax-highlighter.tsx.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +173 to +181
const chatCountForAgent = (tick: number, agentIndex: number): number => {
if (useLegacyChatHeavyCadence) return tick % 6 === 0 ? 1 : 0
const expectedChatEvents = logicalEventsPerAgentPerTick * explicitChatRatio
const baseChatEvents = Math.floor(expectedChatEvents)
const fractional = expectedChatEvents - baseChatEvents
if (fractional <= 0) return baseChatEvents
const denominator = Math.max(1, Math.round(1 / fractional))
return baseChatEvents + ((tick + agentIndex) % denominator === 0 ? 1 : 0)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The current implementation of chatCountForAgent uses a rounding-based denominator approach to distribute fractional chat events over ticks. However, this introduces significant rate-generation errors for certain fractional values.

For example, if expectedChatEvents is 0.7 (e.g., 10 events/sec/agent with a 7% chat ratio):

  • fractional is 0.7
  • denominator is Math.round(1 / 0.7) = Math.round(1.428) = 1
  • (tick + agentIndex) % 1 === 0 is always true
  • This results in returning 1 event every single tick, leading to an actual rate of 1.0 instead of 0.7 (a 42.8% over-generation error).

Similarly, if expectedChatEvents is 0.6, it returns 1 every 2 ticks, leading to a rate of 0.5 instead of 0.6 (a 16.7% under-generation error).

An elegant, stateless, and mathematically perfect way to distribute fractional events over ticks is to use the difference of the cumulative floors:
Math.floor((tick + agentIndex) * expectedChatEvents) - Math.floor((tick + agentIndex - 1) * expectedChatEvents)

This formula guarantees that over any window of $N$ ticks, the total number of generated events will be exactly $\lfloor N \times \text{expectedChatEvents} \rfloor$ or $\lceil N \times \text{expectedChatEvents} \rceil$, perfectly matching the target rate without any rounding errors or complex denominator caps.

          const chatCountForAgent = (tick: number, agentIndex: number): number => {
            if (useLegacyChatHeavyCadence) return tick % 6 === 0 ? 1 : 0
            const expectedChatEvents = logicalEventsPerAgentPerTick * explicitChatRatio
            return Math.floor((tick + agentIndex) * expectedChatEvents) - Math.floor((tick + agentIndex - 1) * expectedChatEvents)
          }

}

if (event.projectId === activeProjectId) {
if ('projectId' in event && event.projectId === activeProjectId) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using the 'projectId' in event check directly can throw a runtime TypeError if event is null or undefined (since typeof null === 'object' but using the in operator on it throws an error). To ensure robust defensive programming, verify that event is a truthy object before using the in operator.

Suggested change
if ('projectId' in event && event.projectId === activeProjectId) {
if (event && typeof event === 'object' && 'projectId' in event && event.projectId === activeProjectId) {

Comment on lines +110 to +111
lang: lang as never,
theme: themeMap[theme] as never

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Casting types to never (e.g., as never) is an anti-pattern that completely disables TypeScript's type checking and can hide potential bugs or breakages during future library updates. It is safer to cast to any if the library types are too complex or not easily importable, or ideally cast to the correct union types imported from the syntax highlighter library (such as BundledLanguage or BundledTheme).

Suggested change
lang: lang as never,
theme: themeMap[theme] as never
lang: lang as any,
theme: themeMap[theme] as any

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

🧹 Nitpick comments (1)
tests/playwright/stress-1000-agents.spec.ts (1)

94-145: ⚡ Quick win

Add a duplicate/replay event assertion to this stress path.

This test currently validates happy-path load and DOM/state consistency, but it does not inject duplicated event_id events to verify dedupe/replay behavior under pressure. Add a small replay batch and assert message count/id-set stability.
Based on learnings: event-streaming test changes should include duplicate/replay cases, not only happy path.

Also applies to: 187-213

🤖 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 `@tests/playwright/stress-1000-agents.spec.ts` around lines 94 - 145, Add a
small duplicate/replay batch after the final broker injection (inside the
chatEveryTicks === null branch after mock.injectBrokerEvents and
requestAnimationFrame) that re-sends a subset of events with identical event_id
values to simulate replay; use the same event_id format used earlier (e.g.,
`stress:final:${index}:chat`) and call mock.injectBrokerEvents with that replay
array, then assert deduplication by checking chatEvents (or the DOM/message id
set) did not increase unexpectedly and that the id set remains stable; ensure
the replay targets the same agent names and includes both a few duplicates and a
couple of unique events to validate stability under pressure.

Source: Learnings

🤖 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/renderer/src/components/common/StatusBar.tsx`:
- Line 106: The StatusBar effect that polls PRs is only keyed on
projectRootPathKey, so branch switches won't re-run the effect until the next
poll; update the dependency array in the StatusBar useEffect (the effect that
triggers git:active-pull-requests / getActivePullRequestsForRoot and ultimately
uses getCurrentBranch) to include the current branch (e.g.,
projectSummary?.branch) so the effect re-initializes immediately on branch
changes, or alternatively add a clear comment documenting the “stale up to
PULL_REQUEST_REFRESH_MS” behavior if you intend to keep the existing timing.

In `@src/renderer/src/components/diff/DiffPane.tsx`:
- Line 1755: The code unsafely casts the result of useProjectStore((s) =>
s.getActiveRoot()) to ProjectRoot; remove the `as ProjectRoot` so TypeScript
infers ProjectRoot | undefined and rely on the existing runtime guards (e.g.,
the `if (!root || !root.pathExists ...)` checks) or, if the component truly
requires a non-null root invariant, replace the cast with an explicit runtime
assertion immediately after retrieving root (throw or assert if root is
undefined). Update any places that assumed the casted non-null type to handle
root as possibly undefined or after the assertion treat it as non-null.

In `@src/renderer/src/lib/syntax-highlighter.tsx`:
- Around line 110-111: The call in highlightCode that passes lang: lang as never
and theme: themeMap[theme] as never circumvents type safety; instead narrow
values to the exact types expected by codeToTokensBase by introducing a small
typed helper (e.g., a function or typed assertion) that maps/validates the
normalized lang against Shiki’s bundledLanguages/bundledLanguagesAlias (falling
back to 'text') and maps Theme to the actual theme type used in themeMap, then
pass the helper's return values to codeToTokensBase without using as never;
update highlightCode to call this helper and pass lang and theme with correct
types so invalid values are rejected by the compiler.

In `@tests/playwright/stress-explorer.spec.ts`:
- Around line 1-2: Wrap every use of appendFileSync in a defensive try/catch so
file-write failures cannot propagate and fail the test; before writing, ensure
the target directory exists (e.g., mkdirSync(dir, { recursive: true })) and on
failure swallow or non-fatal-log the error (console.debug/processLogger.debug)
rather than rethrowing; update all appendFileSync occurrences (including the
ones used in both the normal path and the catch path) so they follow this
pattern to harden result-file writes.

---

Nitpick comments:
In `@tests/playwright/stress-1000-agents.spec.ts`:
- Around line 94-145: Add a small duplicate/replay batch after the final broker
injection (inside the chatEveryTicks === null branch after
mock.injectBrokerEvents and requestAnimationFrame) that re-sends a subset of
events with identical event_id values to simulate replay; use the same event_id
format used earlier (e.g., `stress:final:${index}:chat`) and call
mock.injectBrokerEvents with that replay array, then assert deduplication by
checking chatEvents (or the DOM/message id set) did not increase unexpectedly
and that the id set remains stable; ensure the replay targets the same agent
names and includes both a few duplicates and a couple of unique events to
validate stability under pressure.
🪄 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: 731e496b-32d4-44d0-b881-473d0d16865c

📥 Commits

Reviewing files that changed from the base of the PR and between 4f3a28e and f2abadb.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (20)
  • .gitignore
  • package.json
  • playwright.stress.config.ts
  • src/renderer/src/components/chat/ChatMessage.tsx
  • src/renderer/src/components/common/StatusBar.tsx
  • src/renderer/src/components/diff/DiffPane.tsx
  • src/renderer/src/components/graph/GraphView.tsx
  • src/renderer/src/components/settings/AccountSettings.tsx
  • src/renderer/src/components/terminal/TerminalInstance.tsx
  • src/renderer/src/hooks/use-message-reconciliation.ts
  • src/renderer/src/lib/ipc-electron.ts
  • src/renderer/src/lib/ipc-mock.ts
  • src/renderer/src/lib/ipc.ts
  • src/renderer/src/lib/syntax-highlighter.tsx
  • src/renderer/src/vite-env.d.ts
  • tests/playwright/STRESS-EXPLORATION-REPORT.md
  • tests/playwright/stress-1000-agents.spec.ts
  • tests/playwright/stress-explorer.spec.ts
  • tsconfig.web.json
  • vite.web.config.ts

window.clearInterval(interval)
}
}, [projectRootPathKey, projectSummary?.branch])
}, [projectRootPathKey])

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if activePullRequests API or PR display logic references branch state

# Check if activePullRequests filters by branch
rg -nP -C3 'activePullRequests' --type=ts

# Check if PRs are displayed conditionally based on current branch
rg -nP -C3 'pullRequest.*branch|branch.*pullRequest' --type=tsx

Repository: AgentWorkforce/pear

Length of output: 2871


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the effect in StatusBar.tsx around the dependency array
sed -n '70,140p' src/renderer/src/components/common/StatusBar.tsx

# 2) Re-run PR/branch searches with safe TSX matching (no --type=tsx)
rg -nP -C3 'activePullRequests' src --glob '*.ts' --glob '*.tsx'

rg -nP -C3 'pullRequest.*branch|branch.*pullRequest' src --glob '*.ts' --glob '*.tsx'

Repository: AgentWorkforce/pear

Length of output: 5995


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find any branch -> pull request filtering/usages in the renderer
rg -n --hidden --glob '*.{ts,tsx}' 'projectSummary\?\.(branch|headBranch|currentBranch)|currentBranch|activePullRequests' src/renderer

# Find any pullRequests filtering by pullRequest.branch
rg -n --hidden --glob '*.{ts,tsx}' 'pullRequests.*filter|filter.*pullRequests|pullRequests.*includes|includes.*pullRequests' src/renderer
rg -n --hidden --glob '*.{ts,tsx}' 'pullRequest\.branch' src/renderer

# Also scan the IPC API usage sites (renderer+preload) for branch-based params
rg -n --hidden --glob '*.{ts,tsx}' 'activePullRequests\(|git:active-pull-requests|pull-requests|pullRequests' src/preload src/renderer src/shared/types

Repository: AgentWorkforce/pear

Length of output: 788


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect StatusBar for any usage of projectSummary?.branch/currentBranch when displaying PRs
sed -n '1,220p' src/renderer/src/components/common/StatusBar.tsx

# Find the IPC handler implementation for 'git:active-pull-requests'
rg -n --hidden --glob '*.{ts,tsx}' "git:active-pull-requests|active-pull-requests|activePullRequests" src

Repository: AgentWorkforce/pear

Length of output: 9592


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '430,560p' src/main/ipc-handlers.ts

Repository: AgentWorkforce/pear

Length of output: 5144


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the implementation of getActivePullRequests in the main git layer
rg -n --hidden --glob '*.{ts,js}' 'getActivePullRequests' src/main src/shared

# Print the implementation region (first match) with a bit of surrounding context
file=$(rg -l --hidden --glob '*.{ts,js}' 'getActivePullRequests' src/main src/shared | head -n 1)
echo "Using file: $file"
sed -n '1,240p' "$file" | head -n 240

Repository: AgentWorkforce/pear

Length of output: 9087


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1280,1420p' src/main/git.ts

Repository: AgentWorkforce/pear

Length of output: 4779


PR list refresh is branch-sensitive but the StatusBar effect won’t re-run until the next tick/root change.

git:active-pull-requests ultimately calls getActivePullRequestsForRoot, which derives the lookup from getCurrentBranch(path) and queries PRs for that branch. Since StatusBar only re-initializes its polling effect on [projectRootPathKey], switching branches won’t refresh the PR indicator until the next PULL_REQUEST_REFRESH_MS (60s) interval tick (or a root/project change). If intentional, document this “stale up to 60s” behavior; otherwise include the current branch (e.g., projectSummary?.branch) in the dependency array to refresh immediately on branch switches.

🤖 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/renderer/src/components/common/StatusBar.tsx` at line 106, The StatusBar
effect that polls PRs is only keyed on projectRootPathKey, so branch switches
won't re-run the effect until the next poll; update the dependency array in the
StatusBar useEffect (the effect that triggers git:active-pull-requests /
getActivePullRequestsForRoot and ultimately uses getCurrentBranch) to include
the current branch (e.g., projectSummary?.branch) so the effect re-initializes
immediately on branch changes, or alternatively add a clear comment documenting
the “stale up to PULL_REQUEST_REFRESH_MS” behavior if you intend to keep the
existing timing.

const windowFocused = useWindowFocused()
const activeProject = useProjectStore((s) => s.getActiveProject())
const root = useProjectStore((s) => s.getActiveRoot())
const root = useProjectStore((s) => s.getActiveRoot()) as ProjectRoot

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 | ⚡ Quick win

Unsafe type assertion bypasses null safety.

The cast as ProjectRoot assumes getActiveRoot() never returns undefined, but the component has guards like if (!root || !root.pathExists ...) at line 2210 and elsewhere, indicating root can be nullable. This type assertion removes TypeScript's null-checking protection.

Instead, let TypeScript infer the type as ProjectRoot | undefined and rely on existing guards, or add a runtime assertion immediately after if this assumption is a component invariant.

🛡️ Recommended fix to preserve type safety
-  const root = useProjectStore((s) => s.getActiveRoot()) as ProjectRoot
+  const root = useProjectStore((s) => s.getActiveRoot())

The existing guards throughout the component already handle undefined correctly.

📝 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 root = useProjectStore((s) => s.getActiveRoot()) as ProjectRoot
const root = useProjectStore((s) => s.getActiveRoot())
🤖 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/renderer/src/components/diff/DiffPane.tsx` at line 1755, The code
unsafely casts the result of useProjectStore((s) => s.getActiveRoot()) to
ProjectRoot; remove the `as ProjectRoot` so TypeScript infers ProjectRoot |
undefined and rely on the existing runtime guards (e.g., the `if (!root ||
!root.pathExists ...)` checks) or, if the component truly requires a non-null
root invariant, replace the cast with an explicit runtime assertion immediately
after retrieving root (throw or assert if root is undefined). Update any places
that assumed the casted non-null type to handle root as possibly undefined or
after the assertion treat it as non-null.

Comment on lines +110 to +111
lang: lang as never,
theme: themeMap[theme] as never

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify shiki version evidence and current callsite typing.
rg -n '"shiki"|shiki' package.json
fd -i 'pnpm-lock.yaml|package-lock.json|yarn.lock' -x rg -n 'shiki' {}
rg -n 'codeToTokensBase\(|as never' src/renderer/src/lib/syntax-highlighter.tsx

Repository: AgentWorkforce/pear

Length of output: 2350


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/renderer/src/lib/syntax-highlighter.tsx"

# Show the relevant section around the flagged lines
sed -n '70,150p' "$FILE" | cat -n

# Find how lang/theme/langMap/themeMap are defined and typed in this file
rg -n "langMap|themeMap|\blang\b|\btheme\b" "$FILE"

# Locate codeToTokensBase import and its type usage
rg -n "codeToTokensBase|codeToTokens" "$FILE"

Repository: AgentWorkforce/pear

Length of output: 2791


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/renderer/src/lib/syntax-highlighter.tsx"

sed -n '70,150p' "$FILE" | cat -n
rg -n "langMap|themeMap|\blang\b|\btheme\b" "$FILE"
rg -n "codeToTokensBase|codeToTokens" "$FILE"

Repository: AgentWorkforce/pear

Length of output: 2791


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/renderer/src/lib/syntax-highlighter.tsx"

sed -n '70,150p' "$FILE" | cat -n
rg -n "langMap|themeMap|\blang\b|\btheme\b" "$FILE"
rg -n "codeToTokensBase|codeToTokens" "$FILE"

Repository: AgentWorkforce/pear

Length of output: 2791


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/renderer/src/lib/syntax-highlighter.tsx"

sed -n '70,150p' "$FILE" | cat -n
rg -n "langMap|themeMap|\blang\b|\btheme\b" "$FILE"
rg -n "codeToTokensBase|codeToTokens" "$FILE"

Repository: AgentWorkforce/pear

Length of output: 2791


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"
sed -n '90,130p' "$FILE" | cat -n

Repository: AgentWorkforce/pear

Length of output: 1404


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"

sed -n '90,140p' "$FILE" | cat -n
rg -n "langMap|themeMap" "$FILE"
rg -n "codeToTokensBase|createHighlighter|codeToTokens" "$FILE"
rg -n "as never" "$FILE"

Repository: AgentWorkforce/pear

Length of output: 1794


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"

sed -n '90,140p' "$FILE" | cat -n
rg -n "langMap|themeMap" "$FILE"
rg -n "codeToTokensBase|createHighlighter|codeToTokens" "$FILE"
rg -n "as never" "$FILE"

Repository: AgentWorkforce/pear

Length of output: 1794


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"
wc -l "$FILE"

Repository: AgentWorkforce/pear

Length of output: 111


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"

sed -n '1,220p' "$FILE" | head -n 160

Repository: AgentWorkforce/pear

Length of output: 3691


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate Shiki type definitions in this repo sandbox
# (prefer .d.ts so we can see the exact option types)
fd -a "codeToTokensBase" node_modules/shiki -t f -e d.ts -e ts | head -n 50

# Search for the codeToTokensBase declaration and print a small surrounding window
rg -n "codeToTokensBase" node_modules/shiki -S --glob "*.d.ts" | head -n 50

# Print the declaration around the best match
FILE="$(rg -l "codeToTokensBase" node_modules/shiki -S --glob "*.d.ts" | head -n 1)"
echo "DECL_FILE=$FILE"
if [ -n "${FILE:-}" ]; then
  rg -n "codeToTokensBase" "$FILE" -n
  # Show the block around the first match
  LNUM="$(rg -n "codeToTokensBase" "$FILE" | head -n 1 | cut -d: -f1)"
  START=$((LNUM-20))
  END=$((LNUM+80))
  sed -n "${START},${END}p" "$FILE" | cat -n
fi

Repository: AgentWorkforce/pear

Length of output: 169


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ls -ლა . | head
ls -la node_modules 2>/dev/null || echo "no node_modules/"
fd shiki -t d -a . 2>/dev/null | head -n 50
rg -n "codeToTokensBase" -S --glob "*.d.ts" . | head -n 50

Repository: AgentWorkforce/pear

Length of output: 127


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ls -la . | head -n 50
if [ -d node_modules ]; then
  echo "node_modules exists"
else
  echo "node_modules missing"
fi

# Search for any local Shiki type declarations in the repo (not node_modules)
fd -a "shiki.*d.ts" -t f . 2>/dev/null | head -n 50
rg -n "codeToTokensBase" -S --glob "*.d.ts" . | head -n 50

Repository: AgentWorkforce/pear

Length of output: 2082


🌐 Web query:

Shiki 4.0.2 codeToTokensBase type options lang theme

💡 Result:

In Shiki 4.0.2, codeToTokensBase is a low-level function used for syntax highlighting that returns raw token data [1]. It serves as the replacement for the deprecated codeToThemedTokens API [2][1]. The function signature for codeToTokensBase accepts the highlighter instance, the code string, and an options object [1]. The options object (CodeToTokensBaseOptions) extends TokenizeWithThemeOptions and typically includes the following [1]: lang: Specifies the language (grammar) to use for tokenization. If omitted, it defaults to 'text' [1]. theme: Specifies the theme to use. If omitted, it defaults to the first theme loaded in the highlighter instance [1]. Additional options available via TokenizeWithThemeOptions include [3][1]: - colorReplacements: A map for replacing specific theme colors. - tokenizeMaxLineLength: Limits the line length for tokenization. - tokenizeTimeLimit: Sets a time limit for the tokenization process. - grammarState: Provides an initial grammar state. - grammarContextCode: Contextual code for grammar parsing. - includeExplanation: A boolean that, when set to true, includes scope explanation data for the tokens [1]. Because codeToTokensBase is a lower-level API, it is often recommended to use the higher-level codeToTokens function when possible, which provides a more streamlined interface for most use cases [2][1].

Citations:


🌐 Web query:

Shiki 4.0.2 CodeToTokensBaseOptions TokenizeWithThemeOptions lang theme TypeScript

💡 Result:

In Shiki 4.0.2, the tokenization process leverages codeToTokensBase (which replaced the older codeToThemedTokens API) [1][2]. The configuration types relevant to this process are CodeToTokensBaseOptions and TokenizeWithThemeOptions [3][2]. CodeToTokensBaseOptions This interface is used to configure the behavior when calling codeToTokensBase. It extends TokenizeWithThemeOptions and includes the following primary properties [3][2]: - lang: Specifies the language (grammar) for the code snippet [2]. - theme: Specifies the theme to be used for tokenization [2]. TokenizeWithThemeOptions This interface defines granular options for the tokenization engine itself [2][4]. Key properties include: - colorReplacements: A map of color names to new color values, allowing you to override specific theme colors [2][5]. - includeExplanation: A boolean or the string literal 'scopeName' that determines whether to include detailed information about why a token received a specific color. Setting it to 'scopeName' is a performance-optimized option that includes only the scope names [5][4]. - tokenizeMaxLineLength and tokenizeTimeLimit: Constraints for the tokenization engine [4]. - grammarState and grammarContextCode: Advanced options for controlling the tokenization grammar context [4]. TypeScript Usage To tokenize code in TypeScript, you typically use codeToTokensBase (for direct base tokenization) or codeToTokens (a higher-level API that handles theme resolution automatically) [1][2][6]. Example: import { codeToTokensBase } from 'shiki'; // Basic usage with TypeScript const tokens = await codeToTokensBase(code, { lang: 'typescript', theme: 'vitesse-dark', includeExplanation: 'scopeName' // Example of using TokenizeWithThemeOptions }); In Shiki 4.x, ensure you are using the correct asynchronous shorthands or the createHighlighter factory, as Shiki 4.0.0+ requires Node.js 20+ and has standardized the API surface [7][8][9].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "export (type|interface) Theme|enum Theme|type Theme" src -S
rg -n "`@/stores/ui-store`|ui-store" -S src/renderer/src/lib/syntax-highlighter.tsx

Repository: AgentWorkforce/pear

Length of output: 199


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/renderer/src/stores/ui-store.ts"
sed -n '1,120p' "$FILE" | cat -n

# Also locate ThemeSchema definition if it's elsewhere
rg -n "ThemeSchema|themeSchema|Theme =" "$FILE"

Repository: AgentWorkforce/pear

Length of output: 5536


🌐 Web query:

github shikijs shiki packages/types src options.ts CodeToTokensBaseOptions lang theme types

💡 Result:

In the Shiki library, CodeToTokensBaseOptions is a TypeScript interface defined in @shikijs/types (and historically located in packages/core/src/types/tokens.ts or similar paths in earlier versions) that configures the low-level codeToTokensBase function [1][2]. Key aspects of CodeToTokensBaseOptions include: Languages and Themes: It is a generic interface, defined as CodeToTokensBaseOptions, which allows for type-safe specification of language and theme identifiers [1][2]. Base Configuration: The interface extends TokenizeWithThemeOptions, which provides foundational properties for tokenization, such as handling color replacements [1][2]. Core Options: - lang: Specifies the language of the code to be tokenized. It accepts string literals for supported languages or special identifiers like 'text' or 'ansi' [1][2]. - theme: Specifies the theme to be used for tokenization [2]. If not provided, the function typically defaults to the first loaded theme [2]. Evolution: - The interface was previously associated with the now-deprecated codeToThemedTokens function [2][3]. It was renamed to codeToTokensBase as part of a significant API modernization that introduced the high-level codeToTokens and codeToHast APIs [2][3]. - The options object continues to evolve with the library, recently adding support for advanced features like colorsRendering to manage how multiple themes are rendered via CSS variables [4][5]. For most use cases, users are encouraged to use the high-level shorthands like codeToHtml or codeToTokens, which provide a more convenient API than directly using codeToTokensBase [2][6][3].

Citations:


Remove as never in Shiki codeToTokensBase options.

In src/renderer/src/lib/syntax-highlighter.tsx (highlightCode), the call to codeToTokensBase uses lang: lang as never and theme: themeMap[theme] as never. Since lang is normalized against Shiki’s bundledLanguages/bundledLanguagesAlias (fallback 'text') and theme is already limited by Theme to 'github-dark-default' | 'github-light-default', replace the as never casts with proper narrowing to codeToTokensBase’s lang/theme option types (e.g., a small typed helper) so invalid values can’t slip past type checking.

🤖 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/renderer/src/lib/syntax-highlighter.tsx` around lines 110 - 111, The call
in highlightCode that passes lang: lang as never and theme: themeMap[theme] as
never circumvents type safety; instead narrow values to the exact types expected
by codeToTokensBase by introducing a small typed helper (e.g., a function or
typed assertion) that maps/validates the normalized lang against Shiki’s
bundledLanguages/bundledLanguagesAlias (falling back to 'text') and maps Theme
to the actual theme type used in themeMap, then pass the helper's return values
to codeToTokensBase without using as never; update highlightCode to call this
helper and pass lang and theme with correct types so invalid values are rejected
by the compiler.

Comment on lines +1 to +2
import { appendFileSync } from 'node:fs'
import { expect, test } from '@playwright/test'

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 | 🟠 Major | ⚡ Quick win

Harden result-file writes so logging cannot fail the stress explorer run.

appendFileSync can throw (missing parent dir, permissions). Because this helper is called in both try and catch, a write failure can bubble out and fail the test unexpectedly.

Proposed fix
-import { appendFileSync } from 'node:fs'
+import { appendFileSync, mkdirSync } from 'node:fs'
+import { dirname } from 'node:path'
@@
 const appendResult = (record: RunRecord): void => {
   const resultPath = process.env.STRESS_RESULT_PATH
   if (!resultPath) return
-  appendFileSync(resultPath, `${JSON.stringify({ ...record, recordedAt: new Date().toISOString() })}\n`)
+  try {
+    mkdirSync(dirname(resultPath), { recursive: true })
+    appendFileSync(resultPath, `${JSON.stringify({ ...record, recordedAt: new Date().toISOString() })}\n`)
+  } catch (error) {
+    console.warn('[stress-explorer] failed to append result', error)
+  }
 }

Also applies to: 103-107, 336-348

🤖 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 `@tests/playwright/stress-explorer.spec.ts` around lines 1 - 2, Wrap every use
of appendFileSync in a defensive try/catch so file-write failures cannot
propagate and fail the test; before writing, ensure the target directory exists
(e.g., mkdirSync(dir, { recursive: true })) and on failure swallow or
non-fatal-log the error (console.debug/processLogger.debug) rather than
rethrowing; update all appendFileSync occurrences (including the ones used in
both the normal path and the catch path) so they follow this pattern to harden
result-file writes.

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

3 issues found across 21 files

You’re at about 99% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

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="playwright.stress.config.ts">

<violation number="1" location="playwright.stress.config.ts:11">
P2: The preview server command is not strict about port binding, which can make Playwright target the wrong URL when 4174 is occupied.</violation>
</file>

<file name="src/renderer/src/components/common/StatusBar.tsx">

<violation number="1" location="src/renderer/src/components/common/StatusBar.tsx:106">
P2: Removing `projectSummary?.branch` from this effect dependencies delays PR refresh on branch switch, causing stale PR status for up to the 60s poll interval.</violation>
</file>

<file name="tests/playwright/stress-explorer.spec.ts">

<violation number="1" location="tests/playwright/stress-explorer.spec.ts:179">
P2: The fractional event distribution logic has significant rate-generation errors. When `Math.round(1 / fractional)` rounds to 1 (e.g., for expectedChatEvents=0.7), the modulo condition is always true, generating 1 event per tick instead of 0.7 (42.8% over-generation). Use a cumulative-floor approach for mathematically correct distribution:

return Math.floor((tick + agentIndex) * expectedChatEvents) - Math.floor((tick + agentIndex - 1) * expectedChatEvents)

</file>

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

Re-trigger cubic

timeout: 15_000
},
webServer: {
command: 'npx vite preview --config vite.web.config.ts --host 127.0.0.1 --port 4174',

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: The preview server command is not strict about port binding, which can make Playwright target the wrong URL when 4174 is occupied.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At playwright.stress.config.ts, line 11:

<comment>The preview server command is not strict about port binding, which can make Playwright target the wrong URL when 4174 is occupied.</comment>

<file context>
@@ -0,0 +1,23 @@
+    timeout: 15_000
+  },
+  webServer: {
+    command: 'npx vite preview --config vite.web.config.ts --host 127.0.0.1 --port 4174',
+    url: 'http://127.0.0.1:4174',
+    reuseExistingServer: !process.env.CI,
</file context>

window.clearInterval(interval)
}
}, [projectRootPathKey, projectSummary?.branch])
}, [projectRootPathKey])

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: Removing projectSummary?.branch from this effect dependencies delays PR refresh on branch switch, causing stale PR status for up to the 60s poll interval.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/renderer/src/components/common/StatusBar.tsx, line 106:

<comment>Removing `projectSummary?.branch` from this effect dependencies delays PR refresh on branch switch, causing stale PR status for up to the 60s poll interval.</comment>

<file context>
@@ -103,7 +103,7 @@ export function StatusBar(): React.ReactNode {
       window.clearInterval(interval)
     }
-  }, [projectRootPathKey, projectSummary?.branch])
+  }, [projectRootPathKey])
 
   const statusColor =
</file context>
Suggested change
}, [projectRootPathKey])
}, [projectRootPathKey, projectSummary?.branch])

const baseChatEvents = Math.floor(expectedChatEvents)
const fractional = expectedChatEvents - baseChatEvents
if (fractional <= 0) return baseChatEvents
const denominator = Math.max(1, Math.round(1 / fractional))

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: The fractional event distribution logic has significant rate-generation errors. When Math.round(1 / fractional) rounds to 1 (e.g., for expectedChatEvents=0.7), the modulo condition is always true, generating 1 event per tick instead of 0.7 (42.8% over-generation). Use a cumulative-floor approach for mathematically correct distribution:

return Math.floor((tick + agentIndex) * expectedChatEvents) - Math.floor((tick + agentIndex - 1) * expectedChatEvents)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/playwright/stress-explorer.spec.ts, line 179:

<comment>The fractional event distribution logic has significant rate-generation errors. When `Math.round(1 / fractional)` rounds to 1 (e.g., for expectedChatEvents=0.7), the modulo condition is always true, generating 1 event per tick instead of 0.7 (42.8% over-generation). Use a cumulative-floor approach for mathematically correct distribution:

return Math.floor((tick + agentIndex) * expectedChatEvents) - Math.floor((tick + agentIndex - 1) * expectedChatEvents)


<file context>
@@ -0,0 +1,350 @@
+            const baseChatEvents = Math.floor(expectedChatEvents)
+            const fractional = expectedChatEvents - baseChatEvents
+            if (fractional <= 0) return baseChatEvents
+            const denominator = Math.max(1, Math.round(1 / fractional))
+            return baseChatEvents + ((tick + agentIndex) % denominator === 0 ? 1 : 0)
+          }
</file context>

@codeant-ai

codeant-ai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI.

1 similar comment
@codeant-ai

codeant-ai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI.

@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)
tests/playwright/stress-explorer.spec.ts (2)

235-247: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Make the scheduler honor the configured per-second rate.

Line 235 rounds the per-tick budget once and then clamps it to at least 1, so runs drift badly as soon as tickMs is not an exact 1-second bucket. For example, STRESS_EVENTS_PER_SEC=1 with STRESS_TICK_MS=100 injects 10 events/sec per agent. That means the explorer can report thresholds for a load shape different from the one requested. Track cumulative/fractional budget across ticks instead of rounding each tick independently.

Also applies to: 311-316

🤖 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 `@tests/playwright/stress-explorer.spec.ts` around lines 235 - 247, The
scheduler currently computes logicalEventsPerAgentPerTick with Math.max(1,
Math.round(...)) which forces at least one event per tick and re-rounds every
tick, causing rate drift; change to compute a per-tick fractional budget
(logicalEventsPerAgentPerSecond * (tickMs / 1000)) and track a cumulative
accumulator (per-agent or shared) that adds this fractional budget each tick,
consume Math.floor(accumulator) events and subtract that integer from the
accumulator so fractional remainder carries forward; update chatCountForAgent
and the effectiveChatRatio/legacy chat-heavy branches to use the accumulated
integer budget when deciding chat vs non-chat events (and apply the same
accumulator approach for the logic referenced around lines 311-316) so the
configured per-second rate is honored over time.

280-318: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Flush the last partial PTY aggregate window.

Lines 282-318 only emit PTY traffic on exact defaultPtyAggregateTicks boundaries. A run with 35 ticks and the default aggregate of 10 never flushes ticks 31-35, so the renderer sees less PTY load than configured and ptyEvents is undercounted. Emit once more on the final tick and clamp the aggregation start to the first real tick.

💡 Localized fix
-            const emitPty = effectiveChatRatio < 1 && tick % defaultPtyAggregateTicks === 0
+            const emitPty = effectiveChatRatio < 1
+              && (tick % defaultPtyAggregateTicks === 0 || tick === ticks)
@@
-                    for (let aggregateTick = tick - defaultPtyAggregateTicks + 1; aggregateTick <= tick; aggregateTick += 1) {
+                    const aggregateStart = Math.max(1, tick - defaultPtyAggregateTicks + 1)
+                    for (let aggregateTick = aggregateStart; aggregateTick <= tick; aggregateTick += 1) {
                       aggregateChatCount += chatCountForAgent(aggregateTick, index)
                     }
🤖 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 `@tests/playwright/stress-explorer.spec.ts` around lines 280 - 318, The PTY
aggregation currently only emits on exact defaultPtyAggregateTicks boundaries,
so partial final windows are dropped; fix by clamping the aggregation start when
computing aggregateChatCount (use Math.max(1, tick - defaultPtyAggregateTicks +
1) instead of tick - defaultPtyAggregateTicks + 1) and add a final emit after
the main while loop: if effectiveChatRatio < 1 and (tick %
defaultPtyAggregateTicks) !== 0 then compute the remaining aggregateChatCount
for the final tick window (using the same chatCountForAgent logic and
useLegacyChatHeavyCadence branch), update ptyEvents, build the chunk and call
mock.injectPtyChunk('mock-project', agentName, chunk) for that final partial
window so leftover ticks are flushed.
🤖 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 `@tests/playwright/stress-explorer.spec.ts`:
- Around line 193-207: The heap sampler loop (heapSampler) currently uses a
non-abortable setTimeout(10_000) between sampleHeap() calls which can delay
shutdown; change it so the sleep is abortable or the final sample is triggered
immediately when stopHeapSampling is flipped: either (A) replace the setTimeout
sleep with a Promise that races the timeout against a "stop" resolver (e.g.
create let resolveStop; const stopPromise = new Promise(r => resolveStop = r)
and await Promise.race([timeout, stopPromise]) and call resolveStop() when
setting stopHeapSampling), or (B) keep the loop but, wherever stopHeapSampling
is set to true, call sampleHeap() once (or resolve the stop promise) before
awaiting heapSampler; update references to heapSampler, stopHeapSampling and
sampleHeap accordingly so tests don't wait the full 10s.

---

Outside diff comments:
In `@tests/playwright/stress-explorer.spec.ts`:
- Around line 235-247: The scheduler currently computes
logicalEventsPerAgentPerTick with Math.max(1, Math.round(...)) which forces at
least one event per tick and re-rounds every tick, causing rate drift; change to
compute a per-tick fractional budget (logicalEventsPerAgentPerSecond * (tickMs /
1000)) and track a cumulative accumulator (per-agent or shared) that adds this
fractional budget each tick, consume Math.floor(accumulator) events and subtract
that integer from the accumulator so fractional remainder carries forward;
update chatCountForAgent and the effectiveChatRatio/legacy chat-heavy branches
to use the accumulated integer budget when deciding chat vs non-chat events (and
apply the same accumulator approach for the logic referenced around lines
311-316) so the configured per-second rate is honored over time.
- Around line 280-318: The PTY aggregation currently only emits on exact
defaultPtyAggregateTicks boundaries, so partial final windows are dropped; fix
by clamping the aggregation start when computing aggregateChatCount (use
Math.max(1, tick - defaultPtyAggregateTicks + 1) instead of tick -
defaultPtyAggregateTicks + 1) and add a final emit after the main while loop: if
effectiveChatRatio < 1 and (tick % defaultPtyAggregateTicks) !== 0 then compute
the remaining aggregateChatCount for the final tick window (using the same
chatCountForAgent logic and useLegacyChatHeavyCadence branch), update ptyEvents,
build the chunk and call mock.injectPtyChunk('mock-project', agentName, chunk)
for that final partial window so leftover ticks are flushed.
🪄 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: fcf66611-9772-4d0b-b401-c4179aa47d31

📥 Commits

Reviewing files that changed from the base of the PR and between f2abadb and 54fb4bb.

📒 Files selected for processing (2)
  • tests/playwright/STRESS-EXPLORATION-REPORT.md
  • tests/playwright/stress-explorer.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • tests/playwright/STRESS-EXPLORATION-REPORT.md

Comment on lines +193 to +207
heapSampler = (async () => {
while (!stopHeapSampling) {
try {
await sampleHeap()
} catch {
return
}
await new Promise((resolve) => setTimeout(resolve, 10_000))
}
try {
await sampleHeap()
} catch {
// Ignore a final sampling race if the page closed after the run.
}
})()

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 | 🟠 Major | ⚡ Quick win

Stop the heap sampler without waiting out the full 10s sleep.

Lines 193-200 sleep for 10 seconds between samples, and Lines 419-423 / 440-443 await the sampler after flipping stopHeapSampling. If the run finishes just after a sample, the test can sit idle for almost a full extra interval before writing the record, which both slows parameter sweeps and inflates wallClockMs with sampler wait time. Make the sleep abortable, or trigger the final sample directly on shutdown instead of awaiting the existing timeout.

Also applies to: 419-423, 440-443

🤖 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 `@tests/playwright/stress-explorer.spec.ts` around lines 193 - 207, The heap
sampler loop (heapSampler) currently uses a non-abortable setTimeout(10_000)
between sampleHeap() calls which can delay shutdown; change it so the sleep is
abortable or the final sample is triggered immediately when stopHeapSampling is
flipped: either (A) replace the setTimeout sleep with a Promise that races the
timeout against a "stop" resolver (e.g. create let resolveStop; const
stopPromise = new Promise(r => resolveStop = r) and await Promise.race([timeout,
stopPromise]) and call resolveStop() when setting stopHeapSampling), or (B) keep
the loop but, wherever stopHeapSampling is set to true, call sampleHeap() once
(or resolve the stop promise) before awaiting heapSampler; update references to
heapSampler, stopHeapSampling and sampleHeap accordingly so tests don't wait the
full 10s.

@kjgbot kjgbot force-pushed the claude/stress-exploration branch from 54fb4bb to 16cd553 Compare June 9, 2026 08:35
@codeant-ai

codeant-ai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI.

@kjgbot kjgbot merged commit 8745f2e into main Jun 9, 2026
4 checks passed
@kjgbot kjgbot deleted the claude/stress-exploration branch June 9, 2026 08:41
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.

2 participants