Skip to content

Add --json-stream flag to rebase, finish, and commit commands#635

Open
NoahCardoza wants to merge 3 commits intoiloom-ai:mainfrom
NoahCardoza:feat/issue-634__add-print-json-stream-flags
Open

Add --json-stream flag to rebase, finish, and commit commands#635
NoahCardoza wants to merge 3 commits intoiloom-ai:mainfrom
NoahCardoza:feat/issue-634__add-print-json-stream-flags

Conversation

@NoahCardoza
Copy link
Contributor

@NoahCardoza NoahCardoza commented Feb 18, 2026

Fixes #634

Add --json-stream flag to rebase, finish, and commit commands

Summary

Add --json-stream flag to il rebase, il finish, and il commit commands to support headless/non-interactive usage (e.g., OpenClaw). This flag controls output format and how Claude Code runs when launched for merge conflict resolution (rebase/finish) or validation fixes (commit).

Context

  • il rebase had no JSON output support at all
  • il finish had --json but no --json-stream
  • il commit had --json but no --json-stream, and its validation pipeline (typecheck, lint, test) launches Claude interactively to fix errors — which doesn't work headless
  • il spin and il plan already implement --json-stream (with --print)
  • OpenClaw (and other automation) needs --json-stream to monitor Claude's output non-interactively

Design Decisions

  • --json-stream = progress to stderr, Claude runs headless (like --print --json-stream on spin/plan), streams JSONL to stdout in real-time. The final JSON result object is always appended at the end of the stream.
  • --json and --json-stream are mutually exclusive (on finish and commit which both have --json)
  • No --print flag needed--json-stream alone is sufficient to enable headless Claude and structured output
  • On rebase/finish, flags only affect Claude behavior when launched for conflict resolution. On commit, flags affect Claude behavior when launched for validation fixes (typecheck/lint/test failures).

Changes

Rebase & Finish (Stage 1-3)

  • src/lib/MergeManager.ts — Accept jsonStream in MergeOptions; when true, launch Claude headless with bypassPermissions and passthroughStdout for conflict resolution
  • src/commands/rebase.ts / src/cli.ts — Register --json-stream flag, redirect logger to stderr, pass option through to MergeManager, output final RebaseResult JSON
  • src/commands/finish.ts / src/cli.ts — Register --json-stream flag with --json mutual exclusion, redirect logger to stderr, pass option through to MergeManager, output final FinishResult JSON

Commit (Stage 4)

  • src/commands/commit.ts — Add jsonStream to CommitCommandInput; use isJsonMode (json || jsonStream) for all JSON return paths; pass jsonStream through to ValidationRunner.runValidations()
  • src/cli.ts (commit section) — Register --json-stream option with --json mutual exclusion; --json-stream implies --no-review; redirect logger to stderr; compact JSON output for --json-stream
  • src/lib/ValidationRunner.ts — Thread jsonStream option through runTypecheck(), runLint(), and runTests() down to attemptClaudeFix(); when jsonStream is true, launch Claude headless with bypassPermissions and passthroughStdout (instead of interactive acceptEdits mode)
  • src/types/index.ts — Add jsonStream to ValidationOptions interface

Documentation

  • docs/iloom-commands.md — Document --json-stream flag on rebase, finish, and commit commands

Verification

  • pnpm build — TypeScript compiles successfully
  • pnpm test — All 3902 tests pass
  • il commit --json-stream — Streams JSONL if Claude is invoked for validation fixes
  • il commit --json --json-stream — Errors with mutual exclusion message
  • il rebase --json-stream — Streams JSONL if Claude is invoked for conflict resolution
  • il finish --json --json-stream — Errors with mutual exclusion message

This PR was created automatically by iloom.

@NoahCardoza
Copy link
Contributor Author

NoahCardoza commented Feb 18, 2026

Complexity Assessment

Classification: COMPLEX

Metrics:

  • Estimated files affected: 5
  • Estimated lines of code: 180
  • Breaking changes: No
  • Database migrations: No
  • Cross-cutting changes: Yes
  • File architecture quality: Poor - largest modified files exceed 1500 LOC (finish.ts, cli.ts)
  • Architectural signals triggered: Parameter propagation across architectural layers
  • Overall risk level: Medium

Reasoning: This change implements cross-cutting parameter propagation where the jsonStream flag flows from CLI registration through RebaseCommand/FinishCommand, into MergeManager.rebaseOnMain(), through attemptClaudeConflictResolution(), and finally to launchClaude() options. Modifications occur in two large/complex files (finish.ts: 1544 LOC, cli.ts: 1612 LOC) requiring coordinated updates. Additionally, flag exclusivity validation logic (--json vs --json-stream) must be implemented at the CLI level, adding coordination complexity. These architectural concerns combined with the large file modifications trigger COMPLEX classification despite modest LOC estimates.

@NoahCardoza
Copy link
Contributor Author

NoahCardoza commented Feb 18, 2026

Analysis Phase

  • Research MergeManager and conflict resolution flow
  • Research rebase command structure
  • Research finish command structure and existing --json flag
  • Research existing --json-stream patterns (spin/plan commands)
  • Map cross-cutting data flow for jsonStream option
  • Document findings

Executive Summary

Adding --json-stream to il rebase and il finish requires threading a jsonStream boolean from CLI flags through to MergeManager.attemptClaudeConflictResolution(), which changes Claude from interactive to headless mode for conflict resolution. The core challenge is that launchClaude() currently has no mode where Claude runs headless (with -p --output-format stream-json) but pipes its JSONL output directly to process.stdout -- it either captures stdout (headless) or inherits all stdio (interactive). A new passthrough mode in launchClaude or in how attemptClaudeConflictResolution spawns Claude is needed.

Questions and Key Decisions

Question Answer
The issue says spin/plan already implement --json-stream with --print -- is there an existing pattern to follow? No. Grep of the codebase shows no --json-stream, jsonStream, or --print flags in any command. The referenced pattern does not exist yet in the codebase.
How should Claude's JSONL reach stdout in --json-stream mode? launchClaude() needs a new option (e.g., passthroughStdout: true) that uses stdio: ['pipe', 'inherit', 'pipe'] so Claude's stdout goes directly to process.stdout while still allowing headless CLI flags.
Should RebaseCommand.execute() return a RebaseResult in --json-stream mode? Yes per the issue spec. The final JSON result must be appended to stdout after Claude's JSONL stream ends.

HIGH/CRITICAL Risks

  • launchClaude stdout routing: Currently launchClaude() in headless mode captures stdout to outputBuffer and writes progress to log.stdout (which is process.stderr in JSON mode). For --json-stream, the JSONL must flow to real process.stdout. The current log.stdout.write(text) pattern in the stdout handler (claude.ts:221-234) would incorrectly route JSONL to stderr when withLogger(createStderrLogger(), ...) is active.

Impact Summary

  • 5 files requiring modification: src/types/index.ts, src/lib/MergeManager.ts, src/utils/claude.ts, src/commands/rebase.ts, src/cli.ts, src/commands/finish.ts
  • 3 test files requiring updates: src/lib/MergeManager.test.ts, src/commands/rebase.test.ts, src/commands/finish.test.ts
  • Cross-cutting change affecting 4 layers (CLI -> Command -> MergeManager -> launchClaude)
  • New RebaseResult type needed in types/index.ts

Complete Technical Reference (click to expand for implementation details)

Problem Space Research

Problem Understanding

OpenClaw and other automation tools need non-interactive control of il rebase and il finish. When these commands trigger Claude for merge conflict resolution, Claude currently runs interactively (headless: false, stdio: inherit). The --json-stream flag should make Claude run headless, streaming JSONL to stdout for machine consumption, with a final result JSON appended.

Architectural Context

The existing --json pattern on finish uses withLogger(createStderrLogger(), ...) to redirect progress to stderr, then outputs final JSON to stdout. The --json-stream flag needs the same stderr redirection for progress, but additionally must pipe Claude's real-time JSONL to stdout during conflict resolution -- a capability that launchClaude() doesn't currently support.

Edge Cases Identified

  • No conflicts: When rebase has no conflicts, --json-stream should output only the final RebaseResult JSON (no JSONL stream since Claude wasn't launched).
  • Claude not available: When detectClaudeCli() returns false, attemptClaudeConflictResolution returns false. In --json-stream mode, the error JSON should reflect conflicts weren't resolved.
  • finish --json and --json-stream mutual exclusivity: Must validate at CLI level and reject both being passed.

Codebase Research Findings

Affected Area: MergeManager conflict resolution

Entry Point: src/lib/MergeManager.ts:423 - attemptClaudeConflictResolution()
Dependencies:

  • Uses: launchClaude() from src/utils/claude.ts:131 with headless: false
  • Uses: detectClaudeCli() from src/utils/claude.ts:68
  • Used By: MergeManager.rebaseOnMain() at line 149

Current launchClaude call (MergeManager.ts:462-467):

await launchClaude(prompt, {
  appendSystemPrompt: systemPrompt,
  addDir: worktreePath,
  headless: false,
  allowedTools: rebaseAllowedTools,
})

For --json-stream, this needs to become:

await launchClaude(prompt, {
  appendSystemPrompt: systemPrompt,
  addDir: worktreePath,
  headless: true,
  permissionMode: 'bypassPermissions',
  allowedTools: rebaseAllowedTools,
  // NEW: pipe stdout to process.stdout instead of capturing
})

Affected Area: launchClaude stdout handling

Entry Point: src/utils/claude.ts:131-266 - headless mode stdout handling
Key Issue: Lines 218-235 capture stdout to outputBuffer and write progress to log.stdout. In --json-stream + withLogger(createStderrLogger(), ...), log.stdout is process.stderr. Claude's JSONL would go to stderr, not stdout.

Required Change: A new mode where execa spawns Claude with stdio: ['pipe', 'inherit', 'pipe'] (stdin piped for prompt, stdout inherited for JSONL passthrough, stderr piped for error capture). In this mode, launchClaude wouldn't return the output string since it flows to stdout directly.

Affected Area: RebaseCommand

Entry Point: src/commands/rebase.ts:104 - execute()
Dependencies:

  • Uses: MergeManager.rebaseOnMain() at line 132
  • Does NOT currently use withLogger or createStderrLogger (no JSON mode exists)

Required Changes:

  • RebaseOptions interface (line 10-13): add jsonStream?: boolean
  • execute(): wrap in withLogger(createStderrLogger(), ...) when --json-stream
  • Pass jsonStream: true through MergeOptions to mergeManager.rebaseOnMain()
  • Output final RebaseResult JSON after execution

Affected Area: FinishCommand

Entry Point: src/commands/finish.ts:190 - execute()
Dependencies:

  • Uses: MergeManager.rebaseOnMain() at lines 611 and via executeIssueWorkflow() at line 596
  • Already has --json mode with withLogger(createStderrLogger(), ...) wrapping in cli.ts:573-575

Required Changes:

  • FinishOptions interface (src/types/index.ts:176): add jsonStream?: boolean
  • cli.ts:544: add --json-stream option, mutual exclusivity guard with --json
  • executeIssueWorkflow() (line 606-609): forward jsonStream through mergeOptions
  • cli.ts action: when jsonStream, use same withLogger(createStderrLogger(), ...) pattern

Affected Area: CLI registration

Entry Points:

  • src/cli.ts:630-644 - rebase command registration (no JSON mode, simple try/catch)
  • src/cli.ts:532-579 - finish command registration (has JSON mode pattern)

Rebase needs significant restructuring to add the withLogger wrapping pattern seen in finish.

Architectural Flow Analysis

Data Flow: jsonStream option

Entry Point: CLI flag --json-stream parsed by Commander

Flow Path:

  1. src/cli.ts:635 (rebase) / src/cli.ts:545 (finish) - CLI action receives jsonStream from Commander options
  2. src/commands/rebase.ts:104 - RebaseCommand.execute() receives via RebaseOptions.jsonStream
  3. src/commands/rebase.ts:121-124 - Constructs MergeOptions with jsonStream field
  4. src/lib/MergeManager.ts:45 - rebaseOnMain() receives MergeOptions
  5. src/lib/MergeManager.ts:149 - Passes to attemptClaudeConflictResolution() (currently no options param - needs adding)
  6. src/lib/MergeManager.ts:462 - launchClaude() called with modified options based on jsonStream

Affected Interfaces (ALL must be updated):

  • MergeOptions at src/types/index.ts:348-352 - Add jsonStream?: boolean
  • RebaseOptions at src/commands/rebase.ts:10-13 - Add jsonStream?: boolean
  • FinishOptions at src/types/index.ts:176-184 - Add jsonStream?: boolean
  • ClaudeCliOptions at src/utils/claude.ts:46-63 - Potentially add stdout passthrough option
  • attemptClaudeConflictResolution signature at src/lib/MergeManager.ts:423-426 - Add options parameter or read from MergeOptions

Critical Implementation Note: This is a cross-cutting change affecting 4 layers and 5+ interfaces. The jsonStream boolean must flow from CLI through Command through MergeManager to launchClaude(). Missing any interface will cause silent parameter loss. The attemptClaudeConflictResolution private method currently takes only (worktreePath, conflictedFiles) -- it needs the MergeOptions or at least the jsonStream flag forwarded.

launchClaude stdout passthrough design

The launchClaude function at src/utils/claude.ts:131 currently supports two modes:

  • Headless (headless: true): Uses execa with piped stdout, captures output, returns string
  • Interactive (headless: false): Uses execa with stdio: 'inherit', returns void

A third mode is needed for --json-stream:

  • Headless + passthrough: Uses headless CLI flags (-p --output-format stream-json --verbose) but stdout goes to process.stdout (not captured). Returns void (or returns captured output from a tee).

The simplest approach: add passthroughStdout?: boolean to ClaudeCliOptions. When true AND headless: true, use stdio: ['pipe', 'inherit', 'pipe'] instead of the default piped stdout with manual handler.

Affected Files

  • src/types/index.ts:348-352 - MergeOptions interface: add jsonStream?: boolean
  • src/types/index.ts:176-184 - FinishOptions interface: add jsonStream?: boolean
  • src/utils/claude.ts:46-63 - ClaudeCliOptions interface: add passthrough option
  • src/utils/claude.ts:195-265 - launchClaude() headless mode: add passthrough stdout path
  • src/lib/MergeManager.ts:45-46 - rebaseOnMain(): destructure jsonStream from options
  • src/lib/MergeManager.ts:148-152 - Forward jsonStream to attemptClaudeConflictResolution()
  • src/lib/MergeManager.ts:423-467 - attemptClaudeConflictResolution(): accept options param, use headless: true + passthrough when jsonStream
  • src/commands/rebase.ts:10-13 - RebaseOptions: add jsonStream?: boolean
  • src/commands/rebase.ts:104-151 - execute(): add JSON stream wrapping, pass through options, output RebaseResult
  • src/cli.ts:630-644 - Rebase CLI registration: add --json-stream flag, restructure for withLogger wrapping
  • src/cli.ts:532-579 - Finish CLI registration: add --json-stream flag, mutual exclusivity with --json
  • src/commands/finish.ts:606-609 - executeIssueWorkflow(): forward jsonStream in mergeOptions

Integration Points

  • MergeManager.rebaseOnMain() is called from both RebaseCommand.execute() (rebase.ts:132) and FinishCommand.executeIssueWorkflow() (finish.ts:611)
  • launchClaude() is called from MergeManager.attemptClaudeConflictResolution() (MergeManager.ts:462) and many other places -- changes must not break other callers
  • createStderrLogger() + withLogger() pattern is well-established in cli.ts for JSON mode redirection

Medium Severity Risks

  • Rebase CLI restructuring: The rebase command action (cli.ts:635-644) currently has no withLogger wrapping -- adding --json-stream requires restructuring similar to the finish command pattern, which is moderately invasive.
  • New RebaseResult type: The issue defines RebaseResult with conflictsDetected, claudeLaunched, conflictsResolved fields. MergeManager.rebaseOnMain() currently returns void -- either its return type must change to carry this info, or RebaseCommand must infer the result from try/catch flow.
  • launchClaude return type: Currently returns string | void. Adding passthrough mode means it could return void even in headless mode (when stdout is piped through). Callers relying on the return value need audit.

@NoahCardoza
Copy link
Contributor Author

NoahCardoza commented Feb 18, 2026

Implementation Plan for Issue #634

Summary

Add --json-stream flag to il rebase and il finish commands to support headless/non-interactive usage. When active, Claude runs headless during conflict resolution with JSONL streaming to stdout, progress goes to stderr, and a final JSON result object is appended to stdout. This requires threading a jsonStream option from CLI flags through command classes, into MergeManager, and down to launchClaude() with a new stdout passthrough mode.

Questions and Key Decisions

Question Answer Rationale
How should Claude's JSONL reach stdout in --json-stream mode? Add passthroughStdout: boolean to ClaudeCliOptions. When true + headless, use stdio: ['pipe', 'inherit', 'pipe'] so Claude stdout goes to process.stdout directly. Current headless mode captures stdout to outputBuffer and routes through log.stdout which would incorrectly send JSONL to stderr under withLogger(createStderrLogger()).
How does RebaseCommand get conflict/Claude metadata for the result JSON? Change rebaseOnMain() return type from void to RebaseOutcome with conflictsDetected, claudeLaunched, conflictsResolved fields. Backwards-compatible since callers ignoring return value are unaffected.
Should --json-stream on finish also affect the FinishResult output? Yes. When --json-stream, the final FinishResult JSON is written to stdout via console.log(JSON.stringify(...)) at the end of the stream, same as --json does today. Consistent with the existing --json pattern on finish.

High-Level Execution Phases

  1. Types and interfaces: Add jsonStream to MergeOptions, FinishOptions, RebaseOptions; add RebaseResult and RebaseOutcome types; add passthroughStdout to ClaudeCliOptions
  2. launchClaude passthrough mode: Add stdout passthrough path in launchClaude() for headless + passthroughStdout
  3. MergeManager plumbing: Forward jsonStream to attemptClaudeConflictResolution(), return RebaseOutcome
  4. RebaseCommand --json-stream: Add flag, withLogger wrapping, JSON result output, error handling
  5. FinishCommand --json-stream: Add flag, mutual exclusivity guard, forward through mergeOptions
  6. CLI registration: Wire flags into cli.ts for both commands
  7. Tests: Update MergeManager, rebase, finish test files

Quick Stats

  • 0 files for deletion
  • 6 files to modify (src/types/index.ts, src/utils/claude.ts, src/lib/MergeManager.ts, src/commands/rebase.ts, src/commands/finish.ts, src/cli.ts)
  • 0 new files to create
  • 3 test files to modify (src/lib/MergeManager.test.ts, src/commands/rebase.test.ts, src/commands/finish.test.ts)
  • Dependencies: None
  • Estimated complexity: Medium

Potential Risks (HIGH/CRITICAL only)

  • launchClaude stdout routing: The passthroughStdout mode uses stdio: ['pipe', 'inherit', 'pipe'] which means launchClaude cannot return the captured output string. Callers using passthroughStdout must not expect a return value. This is fine since attemptClaudeConflictResolution already ignores the return value.

Complete Implementation Guide (click to expand for step-by-step details)

Automated Test Cases to Create

Test File: src/lib/MergeManager.test.ts (MODIFY)

Purpose: Test that jsonStream option flows through to launchClaude() with correct headless/passthrough options, and that rebaseOnMain() returns RebaseOutcome.

Click to expand complete test structure (20 lines)
describe('rebaseOnMain with jsonStream', () => {
  it('should pass headless + passthroughStdout to launchClaude when jsonStream is true', async () => {
    // Setup: conflicts detected, Claude available
    // Call: rebaseOnMain(path, { jsonStream: true, force: true })
    // Assert: launchClaude called with { headless: true, permissionMode: 'bypassPermissions', passthroughStdout: true }
  })

  it('should use interactive mode when jsonStream is false/undefined', async () => {
    // Setup: conflicts detected, Claude available
    // Call: rebaseOnMain(path, { force: true })
    // Assert: launchClaude called with { headless: false } (existing behavior)
  })

  it('should return RebaseOutcome with conflictsDetected=true, claudeLaunched=true when conflicts resolved', async () => {
    // Assert return value shape
  })

  it('should return RebaseOutcome with conflictsDetected=false when no conflicts', async () => {
    // Assert return value for clean rebase
  })
})

Test File: src/commands/rebase.test.ts (MODIFY)

Purpose: Test --json-stream flag plumbing through RebaseCommand.

Click to expand complete test structure (25 lines)
describe('execute with jsonStream', () => {
  it('should pass jsonStream through mergeOptions to rebaseOnMain', async () => {
    // Setup: valid worktree, mock rebaseOnMain returning RebaseOutcome
    // Call: command.execute({ jsonStream: true })
    // Assert: mergeManager.rebaseOnMain called with { jsonStream: true, ... }
  })

  it('should return RebaseResult when jsonStream is true', async () => {
    // Setup: valid worktree
    // Call: result = command.execute({ jsonStream: true })
    // Assert: result has { success, conflictsDetected, claudeLaunched, conflictsResolved }
  })

  it('should return void when jsonStream is not set (existing behavior)', async () => {
    // Call: result = command.execute({})
    // Assert: result is undefined
  })

  it('should return error JSON on failure when jsonStream is true', async () => {
    // Setup: rebaseOnMain throws
    // Call: result = command.execute({ jsonStream: true })
    // Assert: result has { success: false, error: '...' }
  })
})

Test File: src/commands/finish.test.ts (MODIFY)

Purpose: Test --json-stream flag on finish, mutual exclusivity with --json.

Click to expand complete test structure (15 lines)
describe('execute with jsonStream', () => {
  it('should pass jsonStream through mergeOptions to rebaseOnMain', async () => {
    // Setup: issue workflow
    // Call: command.execute({ identifier: '123', options: { jsonStream: true } })
    // Assert: mergeManager.rebaseOnMain called with mergeOptions containing jsonStream: true
  })

  it('should work with jsonStream without json flag', async () => {
    // Verify jsonStream doesn't require json flag
  })
})

Files to Modify

1. src/types/index.ts:176-184 (FinishOptions)

Change: Add jsonStream?: boolean field to FinishOptions interface.

// Add after line 183 (json?: boolean)
jsonStream?: boolean  // --json-stream - Stream JSONL output for Claude conflict resolution

2. src/types/index.ts:348-352 (MergeOptions)

Change: Add jsonStream?: boolean field to MergeOptions interface.

// Add after line 351 (repoRoot?: string)
jsonStream?: boolean  // When true, run Claude headless and stream JSONL for conflict resolution

3. src/types/index.ts (new types after line 260)

Change: Add RebaseResult and RebaseOutcome types.

Click to expand complete type definitions (18 lines)
export interface RebaseResult {
  success: boolean
  conflictsDetected: boolean
  claudeLaunched: boolean
  conflictsResolved?: boolean
  error?: string
}

export interface RebaseOutcome {
  conflictsDetected: boolean
  claudeLaunched: boolean
  conflictsResolved: boolean
}

4. src/utils/claude.ts:46-63 (ClaudeCliOptions)

Change: Add passthroughStdout?: boolean to ClaudeCliOptions interface.

// Add after line 62 (sessionId)
passthroughStdout?: boolean  // In headless mode, pipe stdout to process.stdout instead of capturing

5. src/utils/claude.ts:131-266 (launchClaude headless mode)

Change: Add passthrough stdout path when headless && passthroughStdout. Before the current headless block (line 196), check for passthroughStdout and use a different execa configuration.

Click to expand pseudocode for launchClaude passthrough (20 lines)
// At line ~135, destructure passthroughStdout from options
const { ..., passthroughStdout } = options

// At line ~196, before existing `if (headless)` block, add:
if (headless && passthroughStdout) {
  // Headless + passthrough: Claude's stdout goes directly to process.stdout
  // Used for --json-stream where JSONL must reach the caller's stdout
  const subprocess = execa('claude', args, {
    input: prompt,
    timeout: 0,
    ...(addDir && { cwd: addDir }),
    stdio: ['pipe', 'inherit', 'pipe'], // stdin: pipe (for prompt), stdout: inherit (passthrough), stderr: pipe (capture errors)
  })

  await subprocess
  return // No output to return - it went directly to stdout
}

// Existing headless block continues unchanged...
if (headless) { ... }

6. src/lib/MergeManager.ts:6 (import)

Change: Import RebaseOutcome type.

import type { MergeOptions, RebaseOutcome } from '../types/index.js'

7. src/lib/MergeManager.ts:45-46 (rebaseOnMain signature)

Change: Change return type from Promise<void> to Promise<RebaseOutcome>, destructure jsonStream.

async rebaseOnMain(worktreePath: string, options: MergeOptions = {}): Promise<RebaseOutcome> {
  const { dryRun = false, force = false, jsonStream = false } = options

8. src/lib/MergeManager.ts:88-95 (already up-to-date early return)

Change: Return RebaseOutcome instead of bare return.

// Replace the bare return at line 94 with:
return { conflictsDetected: false, claudeLaunched: false, conflictsResolved: false }

9. src/lib/MergeManager.ts:122-128 (dry-run early return)

Change: Return RebaseOutcome.

// Replace the bare return at line 127 with:
return { conflictsDetected: false, claudeLaunched: false, conflictsResolved: false }

10. src/lib/MergeManager.ts:133-176 (rebase try/catch)

Change: Return RebaseOutcome from success path (line ~140 area after "Rebase completed successfully!") and from conflict resolution paths.

For success (no conflicts) at line ~140:

return { conflictsDetected: false, claudeLaunched: false, conflictsResolved: false }

For conflicts resolved by Claude at line ~161:

return { conflictsDetected: true, claudeLaunched: true, conflictsResolved: true }

11. src/lib/MergeManager.ts:149-152 (forward jsonStream to attemptClaudeConflictResolution)

Change: Pass jsonStream option to attemptClaudeConflictResolution.

const resolved = await this.attemptClaudeConflictResolution(
  worktreePath,
  conflictedFiles,
  { jsonStream }
)

12. src/lib/MergeManager.ts:423-467 (attemptClaudeConflictResolution)

Change: Accept options parameter and conditionally set headless + passthrough mode.

Click to expand pseudocode for attemptClaudeConflictResolution changes (15 lines)
private async attemptClaudeConflictResolution(
  worktreePath: string,
  conflictedFiles: string[],
  options: { jsonStream?: boolean } = {}
): Promise<boolean> {
  // ... existing detectClaudeCli check ...

  // At the launchClaude call (line 462-467), conditionally set options:
  await launchClaude(prompt, {
    appendSystemPrompt: systemPrompt,
    addDir: worktreePath,
    headless: options.jsonStream ? true : false,
    ...(options.jsonStream && {
      permissionMode: 'bypassPermissions' as const,
      passthroughStdout: true,
    }),
    allowedTools: rebaseAllowedTools,
  })

  // ... rest unchanged ...
}

13. src/commands/rebase.ts:1-13 (imports and RebaseOptions)

Change: Add jsonStream to RebaseOptions, import RebaseResult, RebaseOutcome.

import type { MergeOptions, RebaseResult, RebaseOutcome } from '../types/index.js'

export interface RebaseOptions {
  force?: boolean
  dryRun?: boolean
  jsonStream?: boolean
}

14. src/commands/rebase.ts:104-151 (execute method)

Change: Return RebaseResult | void instead of void. When jsonStream, pass through mergeOptions, return result JSON.

Click to expand pseudocode for RebaseCommand.execute changes (30 lines)
async execute(options: RebaseOptions = {}): Promise<RebaseResult | void> {
  process.env.ILOOM = '1'

  // Validate worktree (unchanged)
  let worktreePath: string
  try {
    worktreePath = await this.validateWorktreeContext()
  } catch (error) {
    if (options.jsonStream && error instanceof WorktreeValidationError) {
      return { success: false, conflictsDetected: false, claudeLaunched: false, error: error.message }
    }
    // ... existing error handling ...
    throw error
  }

  const mergeOptions: MergeOptions = {
    dryRun: options.dryRun ?? false,
    force: options.force ?? false,
    jsonStream: options.jsonStream ?? false,  // NEW: forward jsonStream
  }

  // Call rebaseOnMain - now returns RebaseOutcome
  const outcome: RebaseOutcome = await this.mergeManager.rebaseOnMain(worktreePath, mergeOptions)

  // Install dependencies (unchanged)
  // Post-rebase build (unchanged)

  // Return result if jsonStream mode
  if (options.jsonStream) {
    return {
      success: true,
      conflictsDetected: outcome.conflictsDetected,
      claudeLaunched: outcome.claudeLaunched,
      conflictsResolved: outcome.conflictsResolved,
    }
  }
}

15. src/commands/finish.ts:596-611 (executeIssueWorkflow mergeOptions)

Change: Forward jsonStream from FinishOptions to MergeOptions.

// At line 606-609, add jsonStream:
const mergeOptions: MergeOptions = {
  dryRun: options.dryRun ?? false,
  force: options.force ?? false,
  jsonStream: options.jsonStream ?? false,  // NEW
}

16. src/cli.ts:630-644 (rebase CLI registration)

Change: Add --json-stream flag, restructure action to support withLogger wrapping and JSON output.

Click to expand pseudocode for rebase CLI changes (35 lines)
program
  .command('rebase')
  .description('Rebase current branch on main with Claude-assisted conflict resolution')
  .option('-f, --force', 'Skip confirmation prompts')
  .option('-n, --dry-run', 'Preview actions without executing')
  .option('--json-stream', 'Stream JSONL output; runs Claude headless for conflict resolution')
  .action(async (options: { force?: boolean; dryRun?: boolean; jsonStream?: boolean }) => {
    const executeAction = async (): Promise<void> => {
      try {
        const { RebaseCommand } = await import('./commands/rebase.js')
        const command = new RebaseCommand()
        const result = await command.execute(options)
        if (options.jsonStream && result) {
          console.log(JSON.stringify(result, null, 2))
        }
        process.exit(0)
      } catch (error) {
        if (options.jsonStream) {
          console.log(JSON.stringify({
            success: false,
            conflictsDetected: false,
            claudeLaunched: false,
            error: error instanceof Error ? error.message : 'Unknown error'
          }, null, 2))
        } else {
          logger.error(`Failed to rebase: ${error instanceof Error ? error.message : 'Unknown error'}`)
        }
        process.exit(1)
      }
    }

    if (options.jsonStream) {
      const jsonLogger = createStderrLogger()
      await withLogger(jsonLogger, executeAction)
    } else {
      await executeAction()
    }
  })

17. src/cli.ts:532-579 (finish CLI registration)

Change: Add --json-stream flag, mutual exclusivity guard with --json, forward to command.

Click to expand pseudocode for finish CLI changes (20 lines)
// After line 544 (.option('--json', ...)):
.option('--json-stream', 'Stream JSONL output; runs Claude headless for conflict resolution')

// Inside the action, at the top (before executeAction), add mutual exclusivity guard:
if (options.json && options.jsonStream) {
  logger.error('--json and --json-stream are mutually exclusive')
  process.exit(1)
}

// Determine if any JSON output mode is active:
const isAnyJsonMode = options.json || options.jsonStream

// Update the executeAction result output (line 553-555):
if (isAnyJsonMode && result) {
  console.log(JSON.stringify(result, null, 2))
}

// Update the withLogger condition (line 572-578):
if (isAnyJsonMode) {
  const jsonLogger = createStderrLogger()
  await withLogger(jsonLogger, executeAction)
} else {
  await executeAction()
}

// Update error handling (line 558-563):
if (isAnyJsonMode) {
  console.log(JSON.stringify({ success: false, error: ... }, null, 2))
}

Detailed Execution Order

Step 1: Types and Interfaces

Files: src/types/index.ts, src/utils/claude.ts

  1. Add jsonStream?: boolean to MergeOptions at src/types/index.ts:351 -> Verify: TypeScript compiles
  2. Add jsonStream?: boolean to FinishOptions at src/types/index.ts:183 -> Verify: TypeScript compiles
  3. Add RebaseResult and RebaseOutcome types to src/types/index.ts after line 260 -> Verify: Types exported
  4. Add passthroughStdout?: boolean to ClaudeCliOptions at src/utils/claude.ts:62 -> Verify: TypeScript compiles

Step 2: launchClaude Passthrough Mode

Files: src/utils/claude.ts

  1. Destructure passthroughStdout from options at line ~135 -> Verify: No compilation errors
  2. Add passthrough path before existing headless block at line ~196 -> Verify: pnpm build succeeds
  3. Add test cases in src/utils/claude.test.ts for passthrough mode -> Verify: pnpm test src/utils/claude.test.ts passes

Step 3: MergeManager Plumbing

Files: src/lib/MergeManager.ts, src/lib/MergeManager.test.ts

  1. Update import to include RebaseOutcome at line 6 -> Verify: Compiles
  2. Change rebaseOnMain() return type to Promise<RebaseOutcome> at line 45 -> Verify: Compiles
  3. Add RebaseOutcome returns at all exit points (lines 94, 127, ~140, ~161) -> Verify: No missing returns
  4. Forward jsonStream to attemptClaudeConflictResolution() at line 149-152 -> Verify: Compiles
  5. Update attemptClaudeConflictResolution() signature at line 423 to accept options -> Verify: Compiles
  6. Conditionally set headless + passthroughStdout in launchClaude call at line 462-467 -> Verify: Compiles
  7. Update tests in src/lib/MergeManager.test.ts -> Verify: pnpm test src/lib/MergeManager.test.ts passes

Step 4: RebaseCommand --json-stream

Files: src/commands/rebase.ts, src/commands/rebase.test.ts

  1. Add jsonStream?: boolean to RebaseOptions at line 12 -> Verify: Compiles
  2. Change execute() return type to Promise<RebaseResult | void> at line 104 -> Verify: Compiles
  3. Forward jsonStream through mergeOptions at line 121-124 -> Verify: Compiles
  4. Add RebaseResult return when jsonStream is set -> Verify: Compiles
  5. Handle errors with JSON result when jsonStream -> Verify: Compiles
  6. Update tests in src/commands/rebase.test.ts -> Verify: pnpm test src/commands/rebase.test.ts passes

Step 5: FinishCommand --json-stream

Files: src/commands/finish.ts, src/commands/finish.test.ts

  1. Forward jsonStream in mergeOptions at finish.ts:606-609 -> Verify: Compiles
  2. Update tests in src/commands/finish.test.ts -> Verify: pnpm test src/commands/finish.test.ts passes

Step 6: CLI Registration

Files: src/cli.ts

  1. Add --json-stream flag to rebase command at line 634 -> Verify: Compiles
  2. Restructure rebase action with withLogger wrapping and JSON output -> Verify: pnpm build succeeds
  3. Add --json-stream flag to finish command after line 544 -> Verify: Compiles
  4. Add mutual exclusivity guard for --json and --json-stream -> Verify: pnpm build succeeds

Step 7: Final Verification

  1. Run pnpm build -> Verify: Clean compilation
  2. Run pnpm test -> Verify: All tests pass

Execution Plan

  1. Run Step 1 (sequential - types/interfaces all other steps depend on)
  2. Run Steps 2, 3 in parallel (launchClaude passthrough and MergeManager plumbing touch different files)
  3. Run Steps 4, 5 in parallel (RebaseCommand and FinishCommand touch different files)
  4. Run Step 6 (sequential - CLI registration depends on command changes from Steps 4-5)
  5. Run Step 7 (sequential - final build + test verification)

Dependencies and Configuration

None

@NoahCardoza
Copy link
Contributor Author

NoahCardoza commented Feb 18, 2026

Implementation Complete

Summary

Added --json-stream flag to il rebase and il finish commands, enabling headless/non-interactive Claude usage for conflict resolution. When active, Claude runs headless with JSONL streaming to stdout and progress redirected to stderr. On finish, --json and --json-stream are mutually exclusive.

Changes Made

  • src/types/index.ts: Added jsonStream to MergeOptions and FinishOptions; added RebaseResult and RebaseOutcome types
  • src/utils/claude.ts: Added passthroughStdout option to ClaudeCliOptions for direct stdout piping in headless mode
  • src/lib/MergeManager.ts: Plumbed jsonStream through rebaseOnMain()attemptClaudeConflictResolution()launchClaude(); changed return type to RebaseOutcome
  • src/commands/rebase.ts: Added jsonStream to RebaseOptions; returns RebaseResult when active
  • src/commands/finish.ts: Forwarded jsonStream through merge options; unified JSON mode handling
  • src/cli.ts: Registered --json-stream flag on both commands; added mutual exclusivity guard on finish; added withLogger wrapping for stderr redirection

Validation Results

  • ✅ Tests: 3414 passed, 23 skipped (106 test files)
  • ✅ Typecheck: Passed
  • ✅ Lint: Passed
  • ✅ Build: Passed

Detailed Changes by File (click to expand)

src/types/index.ts

Changes: Type definitions for new feature

  • Added jsonStream?: boolean to MergeOptions and FinishOptions
  • Added RebaseResult interface (success, conflictsDetected, claudeLaunched, conflictsResolved, error)
  • Added RebaseOutcome interface (conflictsDetected, claudeLaunched, conflictsResolved)

src/utils/claude.ts

Changes: Passthrough stdout mode for headless Claude

  • Added passthroughStdout?: boolean to ClaudeCliOptions
  • New code path: when headless && passthroughStdout, spawns Claude with stdio: ['pipe', 'inherit', 'pipe']
  • 3 new tests covering passthrough behavior

src/lib/MergeManager.ts

Changes: jsonStream plumbing through merge pipeline

  • rebaseOnMain() now returns Promise<RebaseOutcome> with outcome details at all exit points
  • Forwards jsonStream option to attemptClaudeConflictResolution()
  • When jsonStream: true, sets headless: true, permissionMode: 'bypassPermissions', passthroughStdout: true
  • 6 new tests covering jsonStream forwarding and return values

src/commands/rebase.ts

Changes: --json-stream support in RebaseCommand

  • Added jsonStream to RebaseOptions
  • Returns RebaseResult when jsonStream is active
  • Error handling outputs JSON on failure
  • 5 new tests covering flag plumbing and result types

src/commands/finish.ts

Changes: --json-stream forwarding in FinishCommand

  • Forwards jsonStream to merge options in executeIssueWorkflow
  • Unified isJsonMode check to include both --json and --json-stream
  • 3 new tests covering forwarding and result handling

src/cli.ts

Changes: CLI flag registration and orchestration

  • Added --json-stream flag to both rebase and finish commands
  • Mutual exclusivity guard: --json + --json-stream on finish exits with error
  • withLogger(createStderrLogger()) wrapping when jsonStream active
  • Final JSON result output to stdout

@NoahCardoza NoahCardoza force-pushed the feat/issue-634__add-print-json-stream-flags branch 2 times, most recently from 6819ffb to 278d088 Compare February 18, 2026 20:15
Adds --json-stream flag to `il rebase` and `il finish` for headless/non-interactive
Claude usage during conflict resolution. When active, Claude runs headless with JSONL
streaming to stdout and progress redirected to stderr.

- Add jsonStream to MergeOptions/FinishOptions and new RebaseResult/RebaseOutcome types
- Add passthroughStdout mode to launchClaude() for direct stdout piping
- Plumb jsonStream through MergeManager → attemptClaudeConflictResolution → launchClaude
- Add --json-stream CLI flag to both rebase and finish commands
- Enforce mutual exclusivity of --json and --json-stream on finish
- Use compact JSON for --json-stream output (JSONL compatible)

Closes iloom-ai#634

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@NoahCardoza NoahCardoza force-pushed the feat/issue-634__add-print-json-stream-flags branch from 278d088 to 5ed5de8 Compare February 18, 2026 20:47
@NoahCardoza
Copy link
Contributor Author

@acreeger For some reason this test keeps timing out on my machine, but it doesn't seem to be an issue in the pipeline.

 FAIL  src/lib/LoomManager.test.ts > LoomManager > createIloom > should create loom for branch successfully
Error: Test timed out in 10000ms.
If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".

Have you see this? I'm going to try to fix it, but let me know if you have any insight.

@NoahCardoza
Copy link
Contributor Author

NoahCardoza commented Feb 18, 2026

This was not the correct root cause.

Fix: Flaky LoomManager.test.ts timeout

Fixed a pre-existing flaky test in src/lib/LoomManager.test.ts where createIloom tests would intermittently timeout after 10s.

Root cause: The LoomLauncher mock used vi.fn(() => ({ launchLoom: vi.fn().mockResolvedValue(undefined) })). With mockReset: true in vitest.config.ts, vi.fn() implementations are cleared between tests, so after the first test the constructor returned undefined — causing launcher.launchLoom() to hang indefinitely.

Fix: Changed to a class-based factory (class MockLoomLauncher { launchLoom = vi.fn()... }), which survives mockReset since it's a real class constructor, not a vi.fn() implementation.

Why it was flaky: The first test to exercise the createIloomLoomLauncher path always passed (factory still intact). Subsequent tests in the same file would hang because the factory had been reset. Test execution order determined which test timed out.

@acreeger
Copy link
Collaborator

Have you see this? I'm going to try to fix it, but let me know if you have any insight

I've never seen it! I wonder if it happens for you on main? Looks like you fixed it anyway, but that's an odd one

@NoahCardoza
Copy link
Contributor Author

NoahCardoza commented Feb 18, 2026

Investigation: Flaky LoomManager.test.ts timeout failures

Symptoms

  • LoomManager.test.ts (98 tests) passes reliably in isolation (~9-10s)
  • When run as part of the full suite, specific tests intermittently time out at the 10s testTimeout

Root Cause: Fork initialization thundering herd

Vitest is configured with pool: 'forks' and maxForks: 4. When the full suite runs, all 4 forks start simultaneously and spend ~15s doing heavy module compilation (TypeScript transforms). The LoomManager fork's early tests (#⁠4-6) happen to execute during this peak contention window.

Since createIloom has a deep chain of ~20+ await operations on mocked promises, each await yields to the event loop, giving the OS scheduler an opportunity to context-switch to another fork. Under heavy CPU load, these yields take hundreds of milliseconds to resume instead of the normal sub-millisecond.

Evidence (timing instrumentation with LOOM_TEST_TIMING=1)

In isolation (no contention):

Test Duration
#⁠5 should populate both issueUrls and prUrls... 121ms
#⁠6 should create loom for branch successfully 118ms

Under full suite (4 forks competing):

Test Duration Suite timestamp
#⁠1-3 (before other forks start) ~100ms 0.0-0.3s
#⁠4 594ms 0.9s
#⁠5 should populate both issueUrls and prUrls... 14,841ms (120x slower) 15.7s
#⁠6* should create loom for branch successfully 10,488ms 20.9s
#⁠7+ (after forks finish init) ~100ms 21.0s+

The slowdown is confined to the 0.4s-21s window — exactly the fork initialization period. beforeEach is always <2ms, confirming the test body's async chain is what gets starved.

Fix

Added { timeout: 30_000 } to the top-level describe('LoomManager', ...) block. This is not a code bug — it's CPU contention during parallel test execution, and the default 10s timeout is simply too tight for this 98-test suite under load.

Additional fix (stashed, to be applied separately)

The beforeEach has a vi.clearAllMocks() call at line 195 that clears mock implementations set up earlier in the same beforeEach (lines 173-189). This is redundant since vitest config already has mockReset: true and clearMocks: true, and it forces tests to re-establish defaults that should be inherited from the setup. Removing it simplifies the test setup without changing behavior.

@NoahCardoza
Copy link
Contributor Author

@acreeger After further investigation, I believe the initial fix suggestion was false. It appears there's some strange starvation happening on my computer when running all the tests in parallel. If you don't mind, I'd like to update the timeout on this particular file to 30 seconds. Otherwise, the il commit and il finish commands keep trying to launch claude.

…#634

- Add TestOptions import from vitest
- Configure 30 second timeout for LoomManager describe block
- Handle CPU contention under parallel test execution with 98 tests
…ion. Refs iloom-ai#634

- Add `--json-stream` option to commit CLI command with mutual exclusivity check against `--json`
- Implement headless Claude mode in ValidationRunner for streaming JSONL output during fixes
- Pass jsonStream option through validation pipeline to support stdout passthrough
- Update commit command documentation with new flag
- Fix import statement in LoomManager test file
@NoahCardoza NoahCardoza changed the title Add --print and --json-stream flags to rebase and finish commands Add --json-stream flag to rebase, finish, and commit commands Feb 18, 2026
@NoahCardoza
Copy link
Contributor Author

@acreeger I'm happy to report this is ready for review!

As it just so happens, I was able to successfully test the rebase, commit, and finish commands with these new changes, and they all performed beautifully! It ended up being a good thing that I had some flaky tests to battle LOL

@NoahCardoza NoahCardoza marked this pull request as ready for review February 18, 2026 23:58
@NoahCardoza
Copy link
Contributor Author

iloom Session Summary

Key Themes:

  • --json-stream requires a third mode in launchClaude() — neither pure headless (stdout captured) nor interactive (stdio inherited), but headless flags with stdout piped directly to process.stdout.
  • Every Claude launch point in a command's flow must be audited for --json-stream propagation — missing one causes an interactive session to open in a non-interactive context.
  • LoomManager.test.ts timeouts under parallel execution are caused by event loop starvation during the fork initialization thundering herd, not by test logic errors.

Session Details (click to expand)

Key Insights

  • launchClaude() passthrough mode: The function previously had two modes — headless (captures stdout into a buffer, returns string) and interactive (all stdio inherited). --json-stream needs a third mode: headless CLI flags with stdio: ['pipe', 'inherit', 'pipe'] so Claude's JSONL streams directly to process.stdout. Routing through log.stdout would not work because withLogger(createStderrLogger()) redirects log.stdout to process.stderr.

  • withLogger(createStderrLogger()) scoping: When --json-stream is active, all progress output must be redirected to stderr so stdout carries only JSONL. The withLogger wrapper in cli.ts handles this for the top-level command, but any Claude launch deeper in the call stack (e.g., ValidationRunner.attemptClaudeFix()) must independently use passthroughStdout: true + headless: true.

  • ValidationRunner is a hidden Claude launch point: The finish command has two distinct places where Claude can launch interactively — MergeManager.attemptClaudeConflictResolution() (during rebase conflicts) and ValidationRunner.attemptClaudeFix() (when typecheck/lint/tests fail post-merge). The latter was initially missed. jsonStream must be plumbed through runValidations()runTypecheck()/runLint()/runTests()attemptClaudeFix().

  • exactOptionalPropertyTypes TypeScript strictness: With this compiler option enabled, { jsonStream?: boolean } does NOT accept boolean | undefined as a value. Method signatures receiving optional booleans must be written as { jsonStream?: boolean | undefined } to satisfy the type checker.

  • LoomManager test starvation pattern: Vitest pool: 'forks' with maxForks: 4 causes all forks to start simultaneously. During the ~15s fork initialization window, the Node.js event loop in the LoomManager fork gets CPU-starved. Since createIloom has ~20+ sequential await calls, each yield gives the OS scheduler an opportunity to context-switch. Under load, tests that take ~120ms in isolation swell to 6-15 seconds. The starvation is always confined to tests Environment Management Module #4-6 (running at 0.4s-21s into the suite), confirmed with performance.now() instrumentation.

  • vi.clearAllMocks() inside beforeEach clears that same beforeEach's setup: Calling vi.clearAllMocks() partway through a beforeEach wipes implementations set earlier in the same hook. Since vitest config has mockReset: true and clearMocks: true globally, the call is both redundant and destructive — forcing individual tests to re-establish defaults that should be inherited from setup.

  • Class-based mock factories survive mockReset: true: vi.fn(() => ({ method: vi.fn() })) — the constructor implementation gets cleared by mockReset. A class-based factory (class MockFoo { method = vi.fn() }) creates fresh vi.fn() instances per constructor call and is not affected by reset.

Decisions Made

  • passthroughStdout option on ClaudeCliOptions: Rather than adding a new top-level mode enum, a simple boolean flag was added. When headless: true and passthroughStdout: true, stdio becomes ['pipe', 'inherit', 'pipe'] instead of ['pipe', 'pipe', 'pipe'].

  • MergeManager.rebaseOnMain() returns RebaseOutcome instead of void: Changed to return { conflictsDetected, claudeLaunched, conflictsResolved } so RebaseCommand can construct structured JSON output. Backwards-compatible since callers that ignored the return value are unaffected.

  • Compact JSON for --json-stream, pretty-printed for --json: JSON.stringify(result) (no indentation) for --json-stream to maintain valid JSONL (no embedded newlines); JSON.stringify(result, null, 2) for --json. Using pretty-print with --json-stream would break JSONL parsers.

  • Documentation (Stage 4) excluded from scope: Per explicit user instruction, no documentation updates were made to docs/iloom-commands.md in this ticket.

Challenges Resolved

  • MockExecaReturn undefined in claude.test.ts: Three test cases used vi.mocked(execa).mockResolvedValueOnce({...} as MockExecaReturn) where MockExecaReturn was never imported or defined. The existing mockExeca() helper already returns the correctly typed mock — calling mockExeca().mockResolvedValueOnce({...}) is the correct pattern.

  • ESLint prefer-nullish-coalescing: Attempts to use || for combining json and jsonStream flags were rejected by the linter. Must use ?? regardless of falsy-vs-nullish semantics.

Lessons Learned

  • When adding a flag that must prevent interactive sessions, audit all places in the command's call graph where launchClaude() or equivalent is called — not just the most obvious one. A grepping for headless: false or interactive across the affected command's transitive dependencies is safer than tracing manually.

  • The vitest timeout for large test files should account for parallel fork contention, not just isolated execution time. A file that takes 9s alone may need a 30s timeout to be reliable in the full suite.

  • AsyncLocalStorage (used by logger-context.ts) means the logger context does not automatically propagate across new Worker() or forked processes — each fork/subprocess gets the default logger unless explicitly re-wrapped with withLogger.


Generated with 🤖❤️ by iloom.ai

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Add --print and --json-stream flags to rebase and finish commands

2 participants

Comments