Skip to content

Add subagent relationships for Claude Code and Antigravity sessions - #89

Merged
ragaeeb merged 5 commits into
mainfrom
next
Aug 13, 2026
Merged

Add subagent relationships for Claude Code and Antigravity sessions#89
ragaeeb merged 5 commits into
mainfrom
next

Conversation

@ragaeeb

@ragaeeb ragaeeb commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Claude Code sessions now display parent-child relationships with expandable nested rows, fork indicators, and clearer model names.
    • Sub-agent sessions are automatically discovered and associated with their parent sessions.
  • Bug Fixes
    • Conversation deletion now removes related annotations and cleans up files recreated during deletion.
    • Internal orphan sub-agent threads no longer appear in listings, counts, or dashboard views.
  • Chores
    • Added package publishing and packaged UI smoke-test checks.

GPT 5.6 Luna

Read AGENTS.md. I couldn’t reproduce the current clean install failure; the packed artifact starts successfully. The missing protection was that no test exercised the published tarball through bunx.

Implemented:

The gate packs the package, installs it in a clean temp consumer, runs exact bunx, and verifies SSR HTML—catching missing Vite runtime chunks before publication.

Verified:

  • 554 Bun tests passed
  • Full coverage passed: root 92.54%, UI 91.18%
  • Lint passed
  • Typecheck passed
  • Build passed
  • prepublishOnly passed
  • No spawned smoke processes remain

GPT 5.6 Luna

Implemented TDD-style.

  • Claude Code now discovers nested subagents/*.jsonl files and reads their .meta.json metadata.
  • Child sessions render nested beneath parents with a fork indicator.
  • The child title now correctly shows Implement fingerprint #100 and #101.
  • Its model displays as Claude Opus 5; parent sessions remain Claude Fable 5.
  • Missing-parent subagents safely remain top-level.
  • Direct child-session loading is covered by regression tests.

Key files: claude-code-db.ts, Claude sessions table, regression tests.

The repeated parent prompt/title is expected: these are separate parent sessions launched from the same coordinator prompt; child metadata provides the distinct sub-agent titles.

Release protection also remains in place: prepublishOnly now runs the packed bunx smoke test via package.json. The exact Vite error was not reproducible in a clean local tarball, so publishing now fails if the real packaged consumer path breaks.

Verification: root 555 pass, UI 313 pass, typecheck, lint, build, package smoke, and coverage all pass. The live table showed the expected nesting before the referenced Claude JSONL records disappeared externally; isolated fixtures verify the behavior without restoring or modifying those records.

GPT 5.6 Sol

Fixed and verified.

Root causes:

  • Antigravity’s annotations/<conversation-id>.pbtxt sidecar was never deleted, allowing summary metadata to linger.
  • An active Antigravity SQLite connection could recreate the conversation database immediately after deletion. Cleanup now performs a bounded settle-and-recheck pass.

Changes:

The requested conversation was deleted. After refresh:

  • No Summary row remains.
  • The detail URL reports “conversation not found.”
  • The UUID has zero filesystem matches.
  • The local API returns null.

A byte-identical backup remains recoverable at .spiracha-backups.

Validation passed: lint, typecheck, build, 557 root tests, 313 UI tests, coverage gates, and git diff --check. Existing unrelated Codex changes were preserved.

GPT 5.6 Sol

Fixed the Antigravity route regression.

A deleted conversation’s 4 KB SQLite file was recreated without the required steps table. Discovery now ignores only this incomplete schema state while still surfacing malformed/corrupt databases.

Verified the real /antigravity route loads without the error. Lint, typecheck, build, 559 root tests, 313 UI tests, coverage, and diff checks pass.

Antigravity has separately recreated the previously deleted summary index entry; this fix preserves legitimate summary-only conversations rather than globally hiding them.

GPT 5.6 Sol

Fixed the Kiro cold-navigation experience.

The underlying Kiro filesystem scan measured only 8–9 ms, so no potentially stale caching was added.

Verification passed:

  • 559 tests
  • Typecheck
  • Lint
  • Production build
  • Live local route verification

The pre-existing Antigravity changes remain untouched.

GPT 5.6 Sol

MiniMax is now optimized consistently with Kiro:

The underlying MiniMax scan measured 64–71 ms, so no stale caching was needed.

Verification passed: 559 tests, lint, typecheck, build, and diff checks.

GPT 5.6 Sol

Claude Code and MiniMax now share the same cold-load optimization:

Verification passed: 559 tests, lint, typecheck, production build, and diff checks. One unrelated file-watcher test timed out on the first run, then passed focused and in the complete rerun.

GPT 5.6 Sol

Implemented and verified.

  • Grok now preloads from navigation and shows an immediate loading panel instead of a blank wait.
  • Cursor now reads the modern composerHeaders table.
  • Multi-root workspace assignment is preserved.
  • Subagents render nested beneath the orchestrator.
  • Models and reasoning levels are shown.
  • Archived, aborted zero-message drafts are excluded.
  • Cursor thread detail now includes the model and reasoning metadata.

Live verification found one root orchestrator plus eleven nested subagents. The supplied request ID and message excerpts match. Cursor records that particular reused subagent as Grok 4.5, High reasoning—not Medium/Low—so Spiracha displays High faithfully.

Key changes: cursor-db.ts, cursor-threads-table.tsx, grok.index.tsx.

Validation passed:

  • bun test
  • bun run lint
  • bun run typecheck
  • bun run build
  • Browser smoke with no console errors
  • git diff --check

GPT 5.6 Sol

Fixed the multi-tab loading regression and added Cursor optimizations.

  • Codex project inventories no longer refetch whenever a tab regains focus or reconnects. Six open tabs could indeed amplify the expensive thread scans.
  • Integration routes now preload on immediate hover/focus intent instead of every tab eagerly loading every provider.
  • Cursor participates in intent preloading and displays a zero-delay “Loading Cursor” panel during its cold inventory scan.
  • Added regression coverage for all three behaviors.

Live verification:

  • Planpoint loaded both threads successfully.
  • Cursor loaded its workspace inventory with no browser warnings or errors.
  • Cursor’s uncached scan took about 3.7 seconds locally, but loading feedback now appears immediately and normal pointer interaction begins preloading before the click.

Verification passed:

  • 560 tests
  • UI focused tests
  • Typecheck
  • Lint
  • Production build
  • git diff --check

Key changes: app-shell.tsx, codex-queries.ts, cursor.index.tsx, and Cursor regression test.

GPT 5.6 Luna

Fixed and consolidated.

The apps/ui split was legacy from the removed CLI. It created a second dependency/configuration root, which allowed stale TanStack/React packages to cause the hydrateStart crash. The UI now lives under src/ui, using the root manifest, Vite config, TypeScript config, and node_modules; apps/ui is gone.

Also fixed:

  • bunx spiracha now launches from the package root.
  • Added a root not-found component.
  • Added package-layout and packaged-boot regression tests.
  • Restored React Table v8 compatibility after the accidental v9 dependency bump.
  • Fixed the package smoke-test harness.
  • Verified fresh / and /antigravity browser loads without errors.

Verification:

  • Root tests: 561 passed
  • UI tests: 319 passed
  • fgh tests: 42 passed
  • Typecheck, lint, build, coverage, and packaged smoke test all pass

fgh only imports spiracha/client through its local file:../spiracha dependency; it does not start the UI or load Vite/TanStack browser code, so it was not causing the hydration failure. Its existing dirty files were left untouched. Refresh the older open Spiracha tabs once so they discard their stale Vite module graph.

GPT 5.3 Codex Spark

Implemented and validated the requested fixes.

What changed

  • apps/ui/src/components/claude-code-sessions-table.tsx:20-42

    • SessionTitleCell now uses depth-based indentation (paddingLeft: ${depth * 0.75}rem``) for subagent rows instead of fixed pl-3.
    • Kept the subagent border treatment (border-[var(--border)] border-l-2).
  • src/lib/antigravity-db.ts:1200-1329

    • Replaced the fixed Bun.sleep(10) retry boundary with SQLite-owner coordination using bun:sqlite (BEGIN IMMEDIATE/COMMIT probe) plus bounded lock polling.
    • Added post-cleanup stability checks:
      • Recheck for lock ownership before/after second cleanup pass where relevant.
      • Detect late DB recreation and report incomplete deletion when artifacts cannot be guaranteed absent.
    • Returns incomplete result (deletedConversationIds: []) when cleanup cannot be proven quiescent or artifacts still reappear.
  • src/package-smoke.ts:92-102, 145-167

    • waitForServer now uses a per-attempt remaining-deadline abort signal (AbortSignal.timeout(Math.max(1, deadline - Date.now()))) and ensures cleanup (clearTimeout, abort) in a finally block.
    • Probe error path now terminates the child process with SIGTERM before awaiting stdoutPromise/stderrPromise; response/error handling and final cleanup were preserved.

Test coverage updates

  • src/lib/antigravity-db.test.ts:1110-1160

    • Updated stale expectation to the new contract for recreated DB artifacts.
    • Added/kept coverage that deletion reports incomplete state when the database is recreated after cleanup.
  • apps/ui/src/components/source-tables.vitest.tsx:276-310

    • Extended nested-session assertion to include grandchild depth (depth=2) and assert per-depth indentation (0.75rem, 1.5rem), then adjusted query to avoid duplicate-title collision.

Validation run

  • rtk bun test src/lib/antigravity-db.test.ts src/package-smoke.test.ts
    • Passed: 27 pass, 0 fail
  • rtk bunx vitest run apps/ui/src/components/source-tables.vitest.tsx --config apps/ui/vitest.config.ts
    • Passed: 1 file / 7 tests

No additional requested findings remain open; the earlier prior expectations that assumed successful deletion when recreating DB artifacts were replaced rather than fixed, per your requested semantics.

GPT 5.3 Codex Spark

Root cause identified: CI failed because the UI suite now imports src/lib/antigravity-db.ts in Vitest, and that file had a top-level bun:sqlite import, which Vite cannot bundle for the UI test environment.

Fix applied:

  • src/lib/antigravity-db.ts
    • Removed static import { Database } from 'bun:sqlite'.
    • Added lazy runtime loading of bun:sqlite via Function-based dynamic import inside tryAcquireAntigravityConversationDbOwner.
    • Kept a cached constructor loader so normal Bun runtime behavior is preserved and no CI bundle-time dependency remains.
    • If bun:sqlite is unavailable, ownership checks fallback safely without importing the module at startup.

Validation now passing locally:

  • rtk bun run test:ui
  • rtk bun test src/ui-suite.test.ts
  • rtk bun test src/lib/antigravity-db.test.ts src/package-smoke.test.ts
  • rtk bunx vitest run apps/ui/src/components/source-tables.vitest.tsx --config apps/ui/vitest.config.ts

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

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

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bec1f2ad-38a0-4451-9bf8-8d1aa703c5fa

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The change adds Claude Code sub-agent discovery and nested rendering, expands Antigravity deletion cleanup, filters internal Codex guardian threads, and adds packaged UI smoke testing to the publish workflow.

Changes

Claude Code session hierarchy

Layer / File(s) Summary
Claude session metadata and discovery
src/lib/claude-code-exporter-types.ts, src/lib/claude-code-db.ts, src/lib/claude-code-db.test.ts, apps/ui/src/lib/*vitest.ts, src/lib/claude-code-transcript.test.ts
Claude Code summaries now include parent session metadata. Discovery recursively processes sub-agent transcripts and reads their metadata. Tests cover parent and child sessions.
Nested session rendering and actions
apps/ui/src/components/claude-code-sessions-table.tsx, apps/ui/src/components/source-tables.vitest.tsx, apps/ui/src/routes/claude-code-sessions.$sessionId.tsx
The UI renders session trees with depth markers and formatted model labels. Export and delete actions remove internal tree fields.

Antigravity conversation cleanup

Layer / File(s) Summary
Conversation artifact deletion and validation
src/lib/antigravity-db.ts, src/lib/antigravity-db.test.ts
Deletion removes annotation and conversation artifacts through a centralized helper and a delayed second cleanup pass. Tests cover recreated database files.

Codex guardian thread filtering

Layer / File(s) Summary
User-visible thread filtering and coverage
src/lib/codex-browser-db.ts, src/lib/codex-browser-db.test.ts
Thread listings, project aggregates, dashboard totals, and recent threads exclude guardian subagents when thread_source is available. Tests retain linked children and exclude orphan rows.

Packaged UI smoke verification

Layer / File(s) Summary
Packaged UI smoke-test runner
src/package-smoke.ts
The smoke test packages the project, launches the tarball through bunx, checks the UI response, reports process output on failure, and cleans temporary files.
Publishing workflow and smoke-test coverage
package.json, src/package-manifest.test.ts, src/package-smoke.test.ts
Publishing runs the build and package smoke test. Tests validate script wiring, tarball paths, and packaged HTML responses.

Sequence Diagram(s)

sequenceDiagram
  participant ClaudeCodeDb
  participant TranscriptFiles
  participant SessionTable
  ClaudeCodeDb->>TranscriptFiles: discover parent and sub-agent transcripts
  TranscriptFiles->>ClaudeCodeDb: return transcript metadata
  ClaudeCodeDb->>SessionTable: provide sessions with parentSessionId
  SessionTable->>SessionTable: build and render the nested session tree
Loading
sequenceDiagram
  participant PackageSmoke
  participant Bunx
  participant SpirachaUI
  PackageSmoke->>PackageSmoke: create package and select port
  PackageSmoke->>Bunx: launch packaged CLI
  Bunx->>SpirachaUI: serve packaged UI
  PackageSmoke->>SpirachaUI: poll and validate HTML
  PackageSmoke->>Bunx: stop process and clean files
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately identifies the Claude Code subagent hierarchy work, which is a major part of the changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch next

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.

@ragaeeb

ragaeeb commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@apps/ui/src/components/claude-code-sessions-table.tsx`:
- Around line 34-40: Update SessionTitleCell so nested sessions use depth-based
left padding rather than the fixed pl-3 class. Preserve the existing border
styling for subagents, and ensure each increase in depth produces an additional
indentation level while top-level sessions remain unpadded.

In `@src/lib/antigravity-db.ts`:
- Around line 1219-1224: Replace the fixed Bun.sleep(10) retry boundary in the
deletion flow surrounding removeConversationFromSummaryIndex and
removeAntigravityConversationPaths with coordination with the active SQLite
connection owner. If coordination cannot guarantee quiescence, verify all
conversation artifacts are absent after cleanup and report incomplete deletion
rather than returning success; add coverage that recreates the database after
the second cleanup pass.

In `@src/package-smoke.ts`:
- Around line 92-102: Update the probe request around fetch in the package smoke
flow to pass an abort signal using the remaining startup deadline, calculated as
AbortSignal.timeout(Math.max(1, deadline - Date.now())). Ensure the signal is
cleared after each request attempt completes, including failures, while
preserving the existing response handling and retry behavior.
- Around line 145-162: Update the catch path around the process probe so proc is
terminated with SIGTERM before awaiting stdoutPromise and stderrPromise. Then
collect and include the pipe output as currently done, while preserving the
existing finally cleanup and error-message composition.
🪄 Autofix

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: ASSERTIVE

Plan: Pro Plus

Run ID: 4098fd68-51cd-4aee-880e-d88f4ac9f12f

📥 Commits

Reviewing files that changed from the base of the PR and between 3be2727 and 86ad43a.

📒 Files selected for processing (17)
  • apps/ui/src/components/claude-code-sessions-table.tsx
  • apps/ui/src/components/source-tables.vitest.tsx
  • apps/ui/src/lib/claude-code-server.vitest.ts
  • apps/ui/src/lib/claude-code-transcript-events.vitest.ts
  • apps/ui/src/routes/claude-code-sessions.$sessionId.tsx
  • package.json
  • src/lib/antigravity-db.test.ts
  • src/lib/antigravity-db.ts
  • src/lib/claude-code-db.test.ts
  • src/lib/claude-code-db.ts
  • src/lib/claude-code-exporter-types.ts
  • src/lib/claude-code-transcript.test.ts
  • src/lib/codex-browser-db.test.ts
  • src/lib/codex-browser-db.ts
  • src/package-manifest.test.ts
  • src/package-smoke.test.ts
  • src/package-smoke.ts

Comment on lines +34 to +40
const SessionTitleCell = ({ depth, session }: { depth: number; session: ClaudeCodeSessionTreeNode }) => {
const isSubagent = depth > 0;

return (
<div
className={cn('min-w-0', isSubagent ? 'border-[var(--border)] border-l-2 pl-3' : '')}
data-row-depth={depth}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Indent each nested level.

At Line 39, every subagent uses the same pl-3 padding. A grandchild session renders at the same horizontal position as a direct child. Calculate the padding from depth.

Proposed fix
         <div
-            className={cn('min-w-0', isSubagent ? 'border-[var(--border)] border-l-2 pl-3' : '')}
+            className={cn('min-w-0', isSubagent ? 'border-[var(--border)] border-l-2' : '')}
             data-row-depth={depth}
+            style={isSubagent ? { paddingInlineStart: `${depth * 0.75}rem` } : undefined}
         >
📝 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 SessionTitleCell = ({ depth, session }: { depth: number; session: ClaudeCodeSessionTreeNode }) => {
const isSubagent = depth > 0;
return (
<div
className={cn('min-w-0', isSubagent ? 'border-[var(--border)] border-l-2 pl-3' : '')}
data-row-depth={depth}
const SessionTitleCell = ({ depth, session }: { depth: number; session: ClaudeCodeSessionTreeNode }) => {
const isSubagent = depth > 0;
return (
<div
className={cn('min-w-0', isSubagent ? 'border-[var(--border)] border-l-2' : '')}
data-row-depth={depth}
style={isSubagent ? { paddingInlineStart: `${depth * 0.75}rem` } : undefined}
>
🤖 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 `@apps/ui/src/components/claude-code-sessions-table.tsx` around lines 34 - 40,
Update SessionTitleCell so nested sessions use depth-based left padding rather
than the fixed pl-3 class. Preserve the existing border styling for subagents,
and ensure each increase in depth produces an additional indentation level while
top-level sessions remain unpadded.

Comment thread src/lib/antigravity-db.ts
Comment on lines +1219 to +1224
if (deletedSummary || deletedPaths.length > 0) {
await Bun.sleep(10);
for (const root of roots) {
await removeConversationFromSummaryIndex(getAntigravitySummaryIndexPath(root), conversationId);
await removeAntigravityConversationPaths(root, conversationId);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not use a fixed delay as the deletion boundary.

Line 1220 waits 10 ms and then runs one final cleanup pass. A live writer can recreate the database or a SQLite sidecar after that pass. The function then reports deletion while the conversation artifact remains on disk.

Coordinate deletion with the process that owns active SQLite connections. If coordination is not possible, detect and report incomplete cleanup instead of treating a timed second pass as complete. Add a test that recreates the database after the second pass.

🤖 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/lib/antigravity-db.ts` around lines 1219 - 1224, Replace the fixed
Bun.sleep(10) retry boundary in the deletion flow surrounding
removeConversationFromSummaryIndex and removeAntigravityConversationPaths with
coordination with the active SQLite connection owner. If coordination cannot
guarantee quiescence, verify all conversation artifacts are absent after cleanup
and report incomplete deletion rather than returning success; add coverage that
recreates the database after the second cleanup pass.

Comment thread src/package-smoke.ts Outdated
Comment on lines +92 to +102
const response = await fetch(url);
if (response.ok) {
return response;
}

lastError = `HTTP ${response.status}`;
} catch (error) {
lastError = error instanceof Error ? error.message : String(error);
}

await Bun.sleep(250);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

jq -r '.engines.bun' package.json
nl -ba src/package-smoke.ts | sed -n '86,106p'
curl -fsSL https://bun.sh/docs/api/fetch |
  rg -io '.{0,80}(abort|signal|timeout).{0,80}' |
  head -n 20 || true

Repository: ragaeeb/spiracha

Length of output: 203


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Bun engine: '
jq -r '.engines.bun' package.json

printf '\nwaitForServer probe loop:\n'
awk 'NR>=86 && NR<=115' src/package-smoke.ts

printf '\nSearch waitForServer definitions/usages:\n'
grep -Rn "waitForServer\|deadline\|fetch\\(" src/package-smoke.ts || true

Repository: ragaeeb/spiracha

Length of output: 1363


🌐 Web query:

Bun fetch abort signal timeout API documentation

💡 Result:

In Bun, you can handle timeouts for fetch requests using the standard AbortSignal.timeout API or, in more recent versions, a direct timeout option [1][2]. Standard AbortSignal.timeout To set a timeout for a fetch request, pass an AbortSignal created with AbortSignal.timeout(milliseconds) to the signal property in the fetch options [1][3]: const response = await fetch("https://example.com", { signal: AbortSignal.timeout(1000), // Aborts after 1000ms }); Direct timeout option Recent updates to Bun have added a shorthand numeric timeout option to the fetch API for increased convenience [2]: const response = await fetch("https://example.com", { timeout: 1000, // Aborts after 1000ms }); Key Considerations - AbortSignal support: Bun continues to support the standard Web API AbortSignal for canceling requests [2]. If the timeout elapses, the fetch promise will reject with an error (typically a TimeoutError or AbortError) [4]. - Troubleshooting: If you find that AbortSignal.timeout is not behaving as expected in specific networking scenarios (such as when a host drops packets), ensure you are on the latest version of Bun, as ongoing fixes have been implemented to improve signal propagation and socket cancellation [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'STARTUP_TIMEOUT_MS values/usages:\n'
grep -Rn "STARTUP_TIMEOUT_MS\|waitForServer" src/package-smoke.ts || true

printf '\nProbe loop with STARTUP_TIMEOUT_MS nearby:\n'
awk 'NR>=1 && NR<=170' src/package-smoke.ts

printf '\nCheck BOM/nonstandard line encoding in src/package-smoke.ts:\n'
file src/package-smoke.ts
od -An -tx1 -v src/package-smoke.ts | head -n 3

Repository: ragaeeb/spiracha

Length of output: 6288


🌐 Web query:

Bun fetch supports timeout option AbortSignal.timeout documentation

💡 Result:

Yes, Bun supports using AbortSignal.timeout with the fetch API [1][2]. This is the standard Web API approach for implementing request timeouts [3][4]. To use it, you pass an AbortSignal returned by AbortSignal.timeout(milliseconds) to the signal option in your fetch request [1][5]: const response = await fetch("http://example.com", { signal: AbortSignal.timeout(1000), // Aborts the request after 1000ms }); When the timeout is reached, the fetch promise will reject with a TimeoutError DOMException [3][4]. Additionally, Bun has introduced a first-class numeric timeout option for the fetch API, which allows you to specify a timeout directly in the options object [6][7]. According to recent developments, Bun's fetch now supports the following options for more granular control: - timeout: A numeric value (in milliseconds) for the whole-request wall-clock deadline [7]. - connectTimeout: A numeric value for the DNS, TCP, and TLS handshake phases [7]. - socketTimeout: A numeric value for inactivity in either direction [7]. These extensions provide more specific control than the standard Fetch API, though AbortSignal.timeout remains the standard, cross-platform method for implementing timeouts in fetch requests [1][3].

Citations:


Bound each probe request by the remaining startup deadline.

Line 92 calls await fetch(url) without an abort signal. An indefinite fetch can run past STARTUP_TIMEOUT_MS and block the outer timeout. Pass AbortSignal.timeout(Math.max(1, deadline - Date.now())) in fetch options, and clear it after the attempt completes.

🤖 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/package-smoke.ts` around lines 92 - 102, Update the probe request around
fetch in the package smoke flow to pass an abort signal using the remaining
startup deadline, calculated as AbortSignal.timeout(Math.max(1, deadline -
Date.now())). Ensure the signal is cleared after each request attempt completes,
including failures, while preserving the existing response handling and retry
behavior.

Comment thread src/package-smoke.ts
Comment on lines +145 to +162
} catch (error) {
const [stdoutText, stderrText] = await Promise.all([stdoutPromise, stderrPromise]);
throw new Error(
[
error instanceof Error ? error.message : String(error),
stdoutText.trim() ? `stdout:\n${stdoutText}` : '',
stderrText.trim() ? `stderr:\n${stderrText}` : '',
]
.filter(Boolean)
.join('\n\n'),
);
} finally {
proc.kill('SIGTERM');
await Promise.all([
proc.exited.catch(() => undefined),
stdoutPromise.catch(() => ''),
stderrPromise.catch(() => ''),
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

nl -ba src/package-smoke.ts | sed -n '121,163p'
rg -n -C 3 'stdoutPromise|stderrPromise|proc\.kill|Promise\.all' src/package-smoke.ts

Repository: ragaeeb/spiracha

Length of output: 194


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read relevant lines without nl.
awk '{printf "%6d\t%s\n", NR, $0}' src/package-smoke.ts | sed -n '121,163p'

# Locate the relevant symbols with context.
grep -n -C 3 'stdoutPromise\|stderrPromise\|proc\.kill\|Promise\.all' src/package-smoke.ts || true

Repository: ragaeeb/spiracha

Length of output: 3658


🌐 Web query:

Bun process spawn stdout pipe close after process exits Response readable stream

💡 Result:

In Bun, when you spawn a process using Bun.spawn with stdout set to "pipe" (the default), the stdout property is a ReadableStream that automatically closes once the spawned process finishes and all pending output has been read [1][2][3]. Key details regarding this behavior include: 1. Automatic Stream Closure: The ReadableStream represents the pipe connected to the child process's standard output [2][3]. When the process exits, Bun ensures the stream is closed after the remaining data is drained [4][5]. 2. Timing of Exit vs. Stream Completion: The closure of the pipe (and the ending of the stream) may occur slightly before or after the process exit callback (onExit) or the resolution of the exited promise, depending on system timing [2][6]. You should not rely on a specific ordering between the stream ending and the process exit event [2]. 3. Draining: If the process exits before you have finished reading all the stdout data, the pipe remains active until the data is fully consumed [4]. Internally, Bun maintains the stream until the data is drained, at which point the resources are cleaned up [4][5]. 4. Lazy Loading: If you use the lazy: true option, reading does not begin until you access the stdout property [2][7]. Even in this case, the stream will behave normally and close upon process termination and consumption of output. If you encounter issues where the stream appears not to close, ensure you are fully consuming the stream (e.g., using await proc.stdout.text or by piping it to another destination) [2][8]. Internal memory management improvements in Bun have addressed issues related to stream finalization to ensure resources are properly released after the process exits [9].

Citations:


🌐 Web query:

site:docs.bun.sh subprocess kill SIGTERM stdout pipe close immediately

💡 Result:

Bun’s docs indicate:

  • stdout is a ReadableStream when configured as "pipe" (the default for stdout). (bun.com)
  • proc.kill("SIGTERM") sends SIGTERM, but does not guarantee immediate process termination; the child may handle or ignore it. (bun.com)
  • Use await proc.exited to wait until the process actually exits. (bun.com)
const proc = Bun.spawn(cmd, { stdout: "pipe" });

proc.kill("SIGTERM");

await proc.exited;
const output = await proc.stdout.text();

If you need immediate termination, use SIGKILL:

proc.kill("SIGKILL");
await proc.exited;

If stdout remains open after the parent exits, a descendant process likely inherited the pipe; terminate the process group/tree, not only the direct child.

Citations:


🌐 Web query:

Bun subprocess kill stdout pipe closes before exited promise

💡 Result:

In Bun, the subprocess.exited promise resolves when the child process has exited [1][2]. The behavior regarding stdout pipes closing before this promise resolves is a known aspect of how Bun manages subprocess I/O [3][4]. If you are encountering issues where you lose access to stdout data, it is important to note: 1. Pipe Behavior: The stdout property is a ReadableStream [1][5]. When a process exits or is killed, the pipe is closed by the runtime to clean up resources [4]. If you have not fully consumed the stream by the time the process exits, you may lose the remaining buffered data [3]. 2. Consumption Strategy: To ensure you receive all output, you must continue reading from the stdout stream until it is exhausted (i.e., done is true in your reader loop), rather than relying solely on await subprocess.exited to signal that all data has been received [3]. 3. Known Issues and Fixes: Earlier versions of Bun had issues where killing a subprocess while reading its stdout could cause the Bun process to hang or improperly close the stream [3]. These issues have been addressed in subsequent releases [6][3]. Ensure you are using the latest version of Bun to take advantage of these stability improvements. 4. Correct Implementation: Always drain your stdout or stderr streams to completion [3]. A robust pattern involves initiating the reading process (e.g., via a for await loop or a getReader() loop) and concurrently or sequentially waiting for the exited promise [3]. If you find that data is consistently truncated or the stream closes prematurely despite full consumption, verify that your child process is properly flushing its own stdout buffer before exiting [3]. For complex shell-based subprocesses, be aware that some abstractions (like Bun Shell) may have specific limitations regarding streaming output during execution compared to Bun.spawn [7].

Citations:


Terminate the child before reading pipe output.

Lines 145-146 wait for stdoutPromise and stderrPromise while proc is still running. A failed probe can leave the child running and its pipes open, so the catch path waits before finally sends SIGTERM and the publish step can hit the CI job timeout.

Proposed fix
         } catch (error) {
+            proc.kill('SIGTERM');
             const [stdoutText, stderrText] = await Promise.all([stdoutPromise, stderrPromise]);
             throw new Error(
📝 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
} catch (error) {
const [stdoutText, stderrText] = await Promise.all([stdoutPromise, stderrPromise]);
throw new Error(
[
error instanceof Error ? error.message : String(error),
stdoutText.trim() ? `stdout:\n${stdoutText}` : '',
stderrText.trim() ? `stderr:\n${stderrText}` : '',
]
.filter(Boolean)
.join('\n\n'),
);
} finally {
proc.kill('SIGTERM');
await Promise.all([
proc.exited.catch(() => undefined),
stdoutPromise.catch(() => ''),
stderrPromise.catch(() => ''),
]);
} catch (error) {
proc.kill('SIGTERM');
const [stdoutText, stderrText] = await Promise.all([stdoutPromise, stderrPromise]);
throw new Error(
[
error instanceof Error ? error.message : String(error),
stdoutText.trim() ? `stdout:\n${stdoutText}` : '',
stderrText.trim() ? `stderr:\n${stderrText}` : '',
]
.filter(Boolean)
.join('\n\n'),
);
} finally {
proc.kill('SIGTERM');
await Promise.all([
proc.exited.catch(() => undefined),
stdoutPromise.catch(() => ''),
stderrPromise.catch(() => ''),
]);
🤖 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/package-smoke.ts` around lines 145 - 162, Update the catch path around
the process probe so proc is terminated with SIGTERM before awaiting
stdoutPromise and stderrPromise. Then collect and include the pipe output as
currently done, while preserving the existing finally cleanup and error-message
composition.

@ragaeeb ragaeeb self-assigned this Aug 13, 2026
@ragaeeb ragaeeb added gpt-5.6-sol Good for newcomers priority/high High priority task technical-logic Deals with application business logic bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request performance Performance tuning dependencies Dependency upgrades of external modules feature New feature points/8 Very large task, requires breakdown tests Affects Software Validation and Verification labels Aug 13, 2026
@ragaeeb ragaeeb added this to the v2.4.0 milestone Aug 13, 2026
@ragaeeb ragaeeb changed the title Next Add subagent relationships for Claude Code and Antigravity sessions Aug 13, 2026
@ragaeeb
ragaeeb merged commit 2c52bc0 into main Aug 13, 2026
2 checks passed
@ragaeeb
ragaeeb deleted the next branch August 13, 2026 04:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working dependencies Dependency upgrades of external modules documentation Improvements or additions to documentation enhancement New feature or request feature New feature gpt-5.3-codex-spark gpt-5.6-sol Good for newcomers performance Performance tuning points/8 Very large task, requires breakdown priority/high High priority task technical-logic Deals with application business logic tests Affects Software Validation and Verification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant