Add Hexbus output transcripts#30
Conversation
Capture CLI stdout and stderr with global transcript flags so downstream CLIs can persist human-readable or JSONL runs for agent review. Co-authored-by: Cursor <cursoragent@cursor.com>
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR adds process-level transcript capture to the Hexbus CLI. Users can now pass ChangesTranscript Capture System
Sequence Diagram(s)sequenceDiagram
participant User
participant runCli
participant parseTranscriptFlags
participant startOutputTranscript
participant process_stdout as "process.stdout.write"
participant FS as "filesystem"
User->>runCli: invoke CLI with rawArgs
runCli->>parseTranscriptFlags: parse rawArgs (--log-file / --log-format)
parseTranscriptFlags-->>runCli: TranscriptFlagOptions | null
runCli->>startOutputTranscript: startOutputTranscript(options)
startOutputTranscript->>FS: create/truncate transcript file
startOutputTranscript->>process_stdout: patch write to append captured output
process_stdout->>FS: append captured output (text/jsonl)
runCli->>startOutputTranscript: close(exitCode) on shutdown
startOutputTranscript->>FS: write end record (jsonl) and restore writers
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
Keep the transcript logging feature scoped to Hexbus' existing Clack output layer so the branch can ship without unrelated terminal UI changes. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/hexbus/src/errors.ts (1)
302-317:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
withErrorHandlingusesprocess.exit(1)directly, bypassing OpenTUI exit flow.
createErrorHandlers.handleErrorroutes throughopenTuiExit(messages, 1)for consistent rendering, butwithErrorHandlingstill callscliError.display(logger)followed byprocess.exit(1). This inconsistency means errors caught bywithErrorHandlingwon't be captured in transcripts or rendered through the unified OpenTUI path.Consider aligning
withErrorHandlingwith the new pattern, or document the intentional divergence.🔧 Potential alignment with OpenTUI exit
export function withErrorHandling< T extends (...args: unknown[]) => Promise<unknown>, >(fn: T, logger: CliLogger, context?: { command?: string }): T { return (async (...args: Parameters<T>) => { try { return await fn(...args); } catch (error) { const cliError = CliError.from(error); - cliError.display(logger); + let { message } = cliError.entry; + if (cliError.context?.details) { + message += `: ${cliError.context.details}`; + } + const messages = [formatLogMessage("error", message)]; + if (cliError.entry.hint) { + messages.push(formatLogMessage("info", `Hint: ${cliError.entry.hint}`)); + } + if (cliError.entry.docs) { + messages.push(formatLogMessage("info", `Docs: ${cliError.entry.docs}`)); + } if (context?.command) { - logger.info(`Command: ${context.command}`); + messages.push(formatLogMessage("info", `Command: ${context.command}`)); } - process.exit(1); + return openTuiExit(messages, 1); } }) as T; }🤖 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 `@packages/hexbus/src/errors.ts` around lines 302 - 317, The withErrorHandling wrapper currently calls CliError.from/error.display and then process.exit(1), bypassing the OpenTUI exit flow; update it to delegate to the centralized exit handler (e.g., use createErrorHandlers.handleError or call openTuiExit(messages, 1) via the same pathway used by createErrorHandlers) so errors go through the unified rendering/transcript logic—locate the withErrorHandling function, remove the direct process.exit(1) call, and invoke the shared error/exit helper with the CliError (or its message payload and exit code) to preserve consistent OpenTUI behavior.packages/hexbus/src/intro.ts (1)
36-45:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUnused
contextparameter indisplayIntro
displayIntrostill takescontext: Pick<CliContext, "logger">, but it’s not referenced anywhere;openTuiIntroonly accepts(banner, content, title?), whilerunner.tsand therunnerunit tests continue to passcommandContextanyway. Either rename the parameter to_contextand update the JSDoc (sinceloggerisn’t used anymore), or remove it as a breaking API change by updating all call sites (runner.ts, tests, and the exported signature fromsrc/index.ts).🤖 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 `@packages/hexbus/src/intro.ts` around lines 36 - 45, The function displayIntro has an unused parameter context; rename it to _context in the function signature (keep the type Pick<CliContext, "logger">) and update its JSDoc to indicate the logger is intentionally unused to silence linters and clarify intent; leave openTuiIntro calls and caller sites (runner.ts and tests) unchanged since this is not a breaking API change. Ensure the identifier change is applied to the export in src/index.ts if it re-exports the same symbol so the signature remains consistent.
🤖 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 `@packages/hexbus/src/logger.ts`:
- Around line 93-119: The switch over logLevel in packages/hexbus/src/logger.ts
is duplicating openTuiMessage(formattedMessage) in every non-error branch;
simplify by making the "error" case handle its TTY fallback (if
process.stdin.isTTY && process.stdout.isTTY then call
openTuiMessage(formattedMessage) else write to process.stderr), and collapse all
other cases (warn, info, debug, success, failed, default) to a single
fall-through that just calls openTuiMessage(formattedMessage); update the switch
or replace it with an if/else using the existing symbols logLevel,
formattedMessage, openTuiMessage and the TTY check to remove the duplicated
calls.
In `@packages/hexbus/src/opentui.ts`:
- Around line 289-299: getSession retains a rejected sessionPromise so the TUI
is permanently broken; wrap the await of sessionPromise (created via
sessionPromise ??= createSession()) in a try/catch: assign sessionPromise before
awaiting, then in the catch set sessionPromise = null (and leave activeSession
null) and rethrow the error so callers see the failure, ensuring subsequent
calls to getSession will retry createSession; reference sessionPromise,
getSession, createSession, activeSession, and cancelScheduledClose when making
the change.
- Around line 801-838: The non-openTui branch of openTuiExit currently calls
process.exit synchronously and can prevent the runner's finally block (which
calls transcript?.close(...)) from running; change openTuiExit to ensure the
transcript is closed/flushes before exiting when !canUseOpenTui() by awaiting
the shared transcript close hook (or invoking a closeTranscript/flushTranscript
function) prior to calling process.exit — locate openTuiExit and canUseOpenTui
and add an awaited call to the same transcript?.close(...) logic used by
runner.ts (or a centrally exported close function) so the transcript is
guaranteed to be closed before process.exit is invoked.
- Around line 136-140: clearChildren: no change required—box.getChildren()
already returns a snapshot so you can leave clearChildren(box: BoxRenderable)
as-is. getSession: avoid leaving a permanently rejected sessionPromise by
changing the sessionPromise assignment to use a temporary promise (e.g., const p
= createSession()), set sessionPromise = p, then await p.catch(err => { if
(sessionPromise === p) sessionPromise = undefined; throw err; }) so a rejection
clears sessionPromise and allows retries; reference sessionPromise and
createSession. openTuiExit: do not call process.exit(exitCode) synchronously in
the non-TTY (!canUseOpenTui()) branch because it can bypass runCli()'s
finally/await transcript.close; instead either return the exitCode to the caller
(so the caller calls process.exit after cleanup) or schedule the exit
asynchronously (e.g., setImmediate(() => process.exit(exitCode))) so
transcript.close/runCli() finally handlers can run; reference openTuiExit,
runCli, and transcript.close.
In `@packages/hexbus/src/runner.ts`:
- Around line 815-817: The version check calls handleVersionRequest(options,
rawArgs, state.context) before state.context is initialized, so context (and its
logger) is always undefined; either initialize state.context (so logger is
available) before invoking handleVersionRequest or remove the new context
parameter and logger forwarding from handleVersionRequest/printVersionInfo; to
fix, move the context creation (assignment to state.context) to before the if
(await handleVersionRequest(...)) call so handleVersionRequest and
printVersionInfo receive a valid context.logger, or alternatively remove the
context argument from handleVersionRequest and the logger: context?.logger usage
in printVersionInfo to keep behavior unchanged.
- Around line 848-852: handleRunnerError -> state.context.error.handleError ->
handleError -> openTuiExit currently calls process.exit synchronously on
non-TTY, which bypasses the finally block that calls transcript.close in
runner.ts; to fix, change openTuiExit (and its callers like handleError) to not
call process.exit directly but instead surface the intended exit code by
throwing a dedicated ExitError (or returning the code) so the caller
(handleRunnerError/runner.ts) can let the exception propagate and run the
finally block where transcript?.close(...) executes; ensure the thrown ExitError
carries the exit code and update handleError/openTuiExit docs/comments to
reflect the new behavior.
In `@packages/hexbus/src/transcript.ts`:
- Around line 217-232: close() is declared to return Promise<void> but only
performs synchronous I/O and returns Promise.resolve(), which misleads callers;
make it truly asynchronous by converting the body to async and using
non-blocking file writes: mark close(...) as async, replace the synchronous
appendJsonLine/appendFileSync calls with their Promise-based equivalents (e.g.,
use appendJsonLine that returns a Promise or fs.promises.appendFile for
options.format === "jsonl"), await those calls, and then return/allow implicit
resolution; keep restoring originalStdoutWrite/originalStderrWrite and
preserving durationMs/exitCode/timestamp logic referencing options.format,
options.filePath and startedAt.
- Around line 130-156: The argument parsing loop in transcript.ts currently
unconditionally consumes the next token for spaced flags (--log-file,
--log-format) by doing index++ and using rawArgs[index+1], which can skip a
following real argument if the next token is missing or itself a flag. Update
the handling for the "--log-file" and "--log-format" cases inside the for loop
to first check that rawArgs[index + 1] exists and does not start with "--"
before assigning filePath or format and incrementing index; otherwise treat the
flag as having no value (leave filePath/format unchanged or null) so later code
that calls normalizeFormat or valueAfterEquals behaves correctly. Use the
existing valueAfterEquals and normalizeFormat helpers for the equals-form
branches unchanged.
- Around line 214-215: Add a one-time process exit handler to ensure the
original stdout/stderr write functions are restored and the transcript is closed
on unexpected exits: register process.once listeners for 'exit' and signal
events like 'SIGINT' and 'SIGTERM' that call the module/class close() (or an
internal restore method) and restore process.stdout.write and
process.stderr.write back to the saved originals
(captureStdoutWrite/captureStderrWrite should have saved the originals) before
returning; ensure handlers are idempotent so repeated signals don't double-close
and remove or ignore handlers after normal close() completes.
---
Outside diff comments:
In `@packages/hexbus/src/errors.ts`:
- Around line 302-317: The withErrorHandling wrapper currently calls
CliError.from/error.display and then process.exit(1), bypassing the OpenTUI exit
flow; update it to delegate to the centralized exit handler (e.g., use
createErrorHandlers.handleError or call openTuiExit(messages, 1) via the same
pathway used by createErrorHandlers) so errors go through the unified
rendering/transcript logic—locate the withErrorHandling function, remove the
direct process.exit(1) call, and invoke the shared error/exit helper with the
CliError (or its message payload and exit code) to preserve consistent OpenTUI
behavior.
In `@packages/hexbus/src/intro.ts`:
- Around line 36-45: The function displayIntro has an unused parameter context;
rename it to _context in the function signature (keep the type Pick<CliContext,
"logger">) and update its JSDoc to indicate the logger is intentionally unused
to silence linters and clarify intent; leave openTuiIntro calls and caller sites
(runner.ts and tests) unchanged since this is not a breaking API change. Ensure
the identifier change is applied to the export in src/index.ts if it re-exports
the same symbol so the signature remains consistent.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: 05ba3a4c-9143-4948-886a-949a143202e2
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (15)
.changeset/green-buses-log.mdpackages/hexbus/package.jsonpackages/hexbus/src/__tests__/dispatch.test.tspackages/hexbus/src/__tests__/prompts.test.tspackages/hexbus/src/__tests__/spawned-cli.e2e.test.tspackages/hexbus/src/errors.tspackages/hexbus/src/index.tspackages/hexbus/src/intro.tspackages/hexbus/src/logger.tspackages/hexbus/src/opentui.tspackages/hexbus/src/parser.tspackages/hexbus/src/prompts.tspackages/hexbus/src/runner.tspackages/hexbus/src/spinner.tspackages/hexbus/src/transcript.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Write code that is accessible, performant, type-safe, and maintainable with focus on clarity and explicit intent over brevity
Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Keep functions focused and under reasonable cognitive complexity limits
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Group related code together and separate concerns
Don't useeval()or assign directly todocument.cookie
Validate and sanitize user input
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Usenext/heador App Router metadata API for head elements
Use descriptive names for functions, variables, and types
Handle boundary conditions and error states for edge cases
Add comments for complex logic, but prefer self-documenting code
Runbun x ultracite fixbefore comm...
Files:
packages/hexbus/src/index.tspackages/hexbus/src/intro.tspackages/hexbus/src/logger.tspackages/hexbus/src/spinner.tspackages/hexbus/src/errors.tspackages/hexbus/src/transcript.tspackages/hexbus/src/parser.tspackages/hexbus/src/__tests__/spawned-cli.e2e.test.tspackages/hexbus/src/runner.tspackages/hexbus/src/__tests__/prompts.test.tspackages/hexbus/src/__tests__/dispatch.test.tspackages/hexbus/src/opentui.tspackages/hexbus/src/prompts.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Files:
packages/hexbus/src/index.tspackages/hexbus/src/intro.tspackages/hexbus/src/logger.tspackages/hexbus/src/spinner.tspackages/hexbus/src/errors.tspackages/hexbus/src/transcript.tspackages/hexbus/src/parser.tspackages/hexbus/src/__tests__/spawned-cli.e2e.test.tspackages/hexbus/src/runner.tspackages/hexbus/src/__tests__/prompts.test.tspackages/hexbus/src/__tests__/dispatch.test.tspackages/hexbus/src/opentui.tspackages/hexbus/src/prompts.ts
**/index.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Avoid barrel files (index files that re-export everything)
Files:
packages/hexbus/src/index.ts
**/package.json
📄 CodeRabbit inference engine (AGENTS.md)
Use Bun for installs, scripts, tests, and one-off TypeScript execution
Files:
packages/hexbus/package.json
packages/*/package.json
📄 CodeRabbit inference engine (AGENTS.md)
Put package-specific task logic in each package's
package.json
Files:
packages/hexbus/package.json
**/*.{test,spec}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{test,spec}.{ts,tsx,js,jsx}: Write assertions insideit()ortest()blocks
Avoid done callbacks in async tests - use async/await instead
Don't use.onlyor.skipin committed code
Keep test suites reasonably flat - avoid excessivedescribenesting
Files:
packages/hexbus/src/__tests__/spawned-cli.e2e.test.tspackages/hexbus/src/__tests__/prompts.test.tspackages/hexbus/src/__tests__/dispatch.test.ts
🪛 ast-grep (0.42.3)
packages/hexbus/src/transcript.ts
[warning] 44-44: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(${ESCAPE_CHARACTER}\\[[0-?]*[ -/]*[@-~], "g")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
packages/hexbus/src/opentui.ts
[warning] 29-29: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(${ESCAPE_CHARACTER}\\[[0-?]*[ -/]*[@-~], "g")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🪛 markdownlint-cli2 (0.22.1)
.changeset/green-buses-log.md
[warning] 5-5: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🔇 Additional comments (15)
packages/hexbus/src/parser.ts (1)
32-44: LGTM!Also applies to: 208-214
packages/hexbus/src/runner.ts (1)
803-812: LGTM!packages/hexbus/src/index.ts (1)
175-182: LGTM!packages/hexbus/src/__tests__/spawned-cli.e2e.test.ts (1)
51-57: LGTM!Also applies to: 209-260
.changeset/green-buses-log.md (1)
1-5: LGTM!packages/hexbus/package.json (1)
38-38: ⚡ Quick winConfirm
@opentui/core@0.2.15pin and removal of@clack/prompts.
packages/hexbus/package.jsonnow pins@opentui/coreto0.2.15(npm registry has this version), and the GitHub security advisories lookup for@opentui/corereturned no results.@clack/promptshas no remaining TypeScript references inpackages/hexbus.packages/hexbus/src/prompts.ts (1)
249-261: LGTM!Also applies to: 275-291, 303-317, 335-343
packages/hexbus/src/__tests__/prompts.test.ts (1)
13-24: LGTM!Also applies to: 33-33, 55-59, 75-75, 96-96, 164-164
packages/hexbus/src/__tests__/dispatch.test.ts (1)
10-18: LGTM!Also applies to: 22-22, 74-89, 101-111, 115-115, 273-288, 335-335, 477-492
packages/hexbus/src/transcript.ts (1)
43-45: LGTM!The static analysis tool flagged a ReDoS concern, but this is a false positive. The
ESCAPE_CHARACTERvariable is a constant (\x1b), not user input. The resulting regex\x1b\[[0-?]*[ -/]*[@-~]is a standard ANSI escape sequence pattern with no pathological backtracking risk.packages/hexbus/src/logger.ts (1)
183-194: LGTM!packages/hexbus/src/errors.ts (2)
248-254: LGTM!
269-284: LGTM!packages/hexbus/src/spinner.ts (2)
28-49: LGTM!The spinner now emits static progress messages rather than animated frames—a reasonable tradeoff for transcript compatibility and non-TTY environments.
63-82: LGTM!
| function clearChildren(box: BoxRenderable): void { | ||
| for (const child of box.getChildren()) { | ||
| box.remove(child.id); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does @opentui/core BoxRenderable getChildren() return a live internal array or a new copy?
💡 Result:
@opentui/core BoxRenderable getChildren returns a new array (a snapshot), not a live internal array. Evidence: OpenTUI’s own Tree Navigation docs define getChildren to “Get children” and show it used as an array of direct children for iteration (and for operations like remove-by-id), without any indication that mutating the returned array would affect the underlying render tree [1]. Additionally, internal OpenTUI docs treat child access as an ephemeral “render list” built during layout/render traversal rather than exposing internal storage directly [2].
Citations:
- 1: https://anomalyco-opentui.mintlify.app/core-concepts/renderables
- 2: https://simonklee.dk/static/lab/opentui-explained/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/hexbus/src/opentui.ts"
echo "== clearChildren area =="
sed -n '110,170p' "$FILE" | nl -ba
echo
echo "== getSession area =="
sed -n '250,340p' "$FILE" | nl -ba
echo
echo "== openTuiExit area =="
sed -n '760,880p' "$FILE" | nl -ba
echo
echo "== Any transcript close / process.exit call sites =="
rg -n "process\.exit|transcript|renderLines" "$FILE"Repository: inthhq/hexbus
Length of output: 143
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/hexbus/src/opentui.ts"
echo "== clearChildren area =="
sed -n '110,170p' "$FILE" | cat -n
echo
echo "== getSession area =="
sed -n '250,340p' "$FILE" | cat -n
echo
echo "== openTuiExit area =="
sed -n '760,880p' "$FILE" | cat -n
echo
echo "== Any transcript/process.exit/renderLines call sites =="
rg -n "process\.exit|transcript|renderLines" "$FILE"Repository: inthhq/hexbus
Length of output: 8347
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/hexbus/src/opentui.ts"
echo "== sessionPromise / createSession =="
rg -n "sessionPromise|activeSession|createSession" "$FILE"
echo
echo "== openTuiExit signature and surrounding definitions =="
rg -n "export function openTuiExit|function openTuiExit" "$FILE" -n
sed -n '180,360p' "$FILE" | cat -n
echo
sed -n '720,870p' "$FILE" | cat -n
echo
echo "== openTuiExit call sites =="
rg -n "openTuiExit\\(" -S packages | head -n 200Repository: inthhq/hexbus
Length of output: 11761
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/hexbus/src/errors.ts"
sed -n '220,320p' "$FILE" | cat -n
echo
rg -n "transcript|finally|try\\s*\\{|openTuiExit" "$FILE"Repository: inthhq/hexbus
Length of output: 3760
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "transcript" . | head -n 200
echo
rg -n "createErrorHandlers\\(" packages | head -n 200
echo
rg -n "withErrorHandling\\(" packages | head -n 200
echo
rg -n "openTuiExit\\(" packages | head -n 200Repository: inthhq/hexbus
Length of output: 3221
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/hexbus/src/runner.ts"
sed -n '740,900p' "$FILE" | cat -n
echo
echo "== transcript.ts close implementation =="
TS="packages/hexbus/src/transcript.ts"
rg -n "close\\(" "$TS"
sed -n '130,240p' "$TS" | cat -nRepository: inthhq/hexbus
Length of output: 8058
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/hexbus/src/opentui.ts"
rg -n "function canUseOpenTui|canUseOpenTui\\(" "$FILE"
sed -n '1,120p' "$FILE" | cat -nRepository: inthhq/hexbus
Length of output: 4005
Fix getSession rejection stickiness; adjust openTuiExit exit/cleanup behavior; drop clearChildren mutation concern
clearChildren():box.getChildren()comes back as a snapshot/new array, so iterating while callingbox.remove()should not skip elements—no defensive copy needed.getSession():sessionPromise ??= createSession()leaves a rejectedsessionPromisein place forever ifcreateSession()rejects (no reset on rejection), permanently breaking OpenTUI for subsequent calls.openTuiExit(): theprocess.exit(exitCode)in the!canUseOpenTui()branch is synchronous, so it can bypassrunCli()’sfinally { await transcript?.close(...) }and leave transcripts unclosed/incomplete. In the TTY branch,process.exitis delayed, so transcript close may still run before exit.
🤖 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 `@packages/hexbus/src/opentui.ts` around lines 136 - 140, clearChildren: no
change required—box.getChildren() already returns a snapshot so you can leave
clearChildren(box: BoxRenderable) as-is. getSession: avoid leaving a permanently
rejected sessionPromise by changing the sessionPromise assignment to use a
temporary promise (e.g., const p = createSession()), set sessionPromise = p,
then await p.catch(err => { if (sessionPromise === p) sessionPromise =
undefined; throw err; }) so a rejection clears sessionPromise and allows
retries; reference sessionPromise and createSession. openTuiExit: do not call
process.exit(exitCode) synchronously in the non-TTY (!canUseOpenTui()) branch
because it can bypass runCli()'s finally/await transcript.close; instead either
return the exitCode to the caller (so the caller calls process.exit after
cleanup) or schedule the exit asynchronously (e.g., setImmediate(() =>
process.exit(exitCode))) so transcript.close/runCli() finally handlers can run;
reference openTuiExit, runCli, and transcript.close.
| try { | ||
| handleRunnerError(state); | ||
| } finally { | ||
| await transcript?.close(state.errorToHandle === undefined ? 0 : 1); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect how handleError / openTuiExit terminate the process
ast-grep --pattern 'export function openTuiExit($$$) { $$$ }'
rg -nP --type=ts -C5 '\bprocess\.exit\s*\('
rg -nP --type=ts -C3 '\bhandleError\b'Repository: inthhq/hexbus
Length of output: 6247
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the exact runner try/finally region mentioned in the review comment
sed -n '820,870p' packages/hexbus/src/runner.ts
# Inspect runner's handleRunnerError implementation and how it invokes error handling
rg -n "function handleRunnerError|handleRunnerError\\(" packages/hexbus/src/runner.ts
rg -n "handleRunnerError\\(" packages/hexbus/src/runner.ts -n
# Show the relevant part of errors.ts where handleError terminates
sed -n '220,330p' packages/hexbus/src/errors.ts
# Show the full openTuiExit implementation area (including where process.exit is called)
rg -n "function openTuiExit\\b|export function openTuiExit\\b" packages/hexbus/src/opentui.ts
sed -n '740,870p' packages/hexbus/src/opentui.tsRepository: inthhq/hexbus
Length of output: 7131
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show handleRunnerError implementation and the exact call into context.error.handleError
rg -n "function handleRunnerError|const handleRunnerError|handleRunnerError\\s*=" packages/hexbus/src/runner.ts
# Print a focused window around the call site and the function definition
sed -n '730,830p' packages/hexbus/src/runner.ts
sed -n '830,880p' packages/hexbus/src/runner.ts
# Also inspect transcript.close usage if present
rg -n "transcript\\?\\.close|transcript\\.close" packages/hexbus/src/runner.tsRepository: inthhq/hexbus
Length of output: 3761
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find canUseOpenTui() definition and its conditions
rg -n "function canUseOpenTui\\b|canUseOpenTui\\s*\\(" packages/hexbus/src/opentui.ts
# Show around the definition
# (If rg reports multiple hits, grab the closest window around each definition)
rg -n "canUseOpenTui\\b" packages/hexbus/src/opentui.ts -n -C3Repository: inthhq/hexbus
Length of output: 2204
Fix transcript closure on non-TTY error exits (process.exit bypasses finally)
handleRunnerError(state) calls state.context.error.handleError(...) synchronously (packages/hexbus/src/runner.ts), and handleError(...) (packages/hexbus/src/errors.ts) routes to openTuiExit(messages, 1). In packages/hexbus/src/opentui.ts, openTuiExit() calls process.exit(exitCode) synchronously when canUseOpenTui() is false (process.stdin.isTTY && process.stdout.isTTY). In that non-TTY case, the finally { await transcript?.close(...) } block in runner.ts (lines 848-852) will not run, so the JSONL end record/flush can be lost for error runs.
🤖 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 `@packages/hexbus/src/runner.ts` around lines 848 - 852, handleRunnerError ->
state.context.error.handleError -> handleError -> openTuiExit currently calls
process.exit synchronously on non-TTY, which bypasses the finally block that
calls transcript.close in runner.ts; to fix, change openTuiExit (and its callers
like handleError) to not call process.exit directly but instead surface the
intended exit code by throwing a dedicated ExitError (or returning the code) so
the caller (handleRunnerError/runner.ts) can let the exception propagate and run
the finally block where transcript?.close(...) executes; ensure the thrown
ExitError carries the exit code and update handleError/openTuiExit docs/comments
to reflect the new behavior.
Keep version handling on the fast path and make transcript cleanup more robust around missing flag values and process exits. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
♻️ Duplicate comments (1)
packages/hexbus/src/transcript.ts (1)
130-136:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winHandle short-form flags as missing values in
nextFlagValue.Line 135 only excludes
--..., so inputs like--log-file -hor--log-format -vare consumed as values and can start transcript capture unexpectedly. Treat short option-looking tokens as missing values too.🔧 Suggested patch
function nextFlagValue( rawArgs: readonly string[], index: number ): string | undefined { const value = rawArgs[index + 1]; - return value !== undefined && !value.startsWith("--") ? value : undefined; + if (value === undefined || value === "--") { + return undefined; + } + return /^-{1,2}[A-Za-z]/.test(value) ? undefined : value; }As per coding guidelines, "Handle boundary conditions and error states for edge cases".
🤖 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 `@packages/hexbus/src/transcript.ts` around lines 130 - 136, The helper nextFlagValue currently treats only tokens starting with "--" as flags, so short-form flags like "-h" are mistakenly accepted as values; update the check in nextFlagValue (params rawArgs, index) to treat any token that begins with "-" as a flag (e.g., use value !== undefined && !value.startsWith("-") or a regex like /^-/) so short options are considered missing values; keep the same return semantics (return the next token only if it does not look like a flag).
🤖 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.
Duplicate comments:
In `@packages/hexbus/src/transcript.ts`:
- Around line 130-136: The helper nextFlagValue currently treats only tokens
starting with "--" as flags, so short-form flags like "-h" are mistakenly
accepted as values; update the check in nextFlagValue (params rawArgs, index) to
treat any token that begins with "-" as a flag (e.g., use value !== undefined &&
!value.startsWith("-") or a regex like /^-/) so short options are considered
missing values; keep the same return semantics (return the next token only if it
does not look like a flag).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: f884fa4a-9d02-4ce0-96ba-dd0d440ef588
📒 Files selected for processing (4)
packages/hexbus/src/__tests__/transcript.test.tspackages/hexbus/src/parser.tspackages/hexbus/src/runner.tspackages/hexbus/src/transcript.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Write code that is accessible, performant, type-safe, and maintainable with focus on clarity and explicit intent over brevity
Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Keep functions focused and under reasonable cognitive complexity limits
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Group related code together and separate concerns
Don't useeval()or assign directly todocument.cookie
Validate and sanitize user input
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Usenext/heador App Router metadata API for head elements
Use descriptive names for functions, variables, and types
Handle boundary conditions and error states for edge cases
Add comments for complex logic, but prefer self-documenting code
Runbun x ultracite fixbefore comm...
Files:
packages/hexbus/src/__tests__/transcript.test.tspackages/hexbus/src/parser.tspackages/hexbus/src/runner.tspackages/hexbus/src/transcript.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Files:
packages/hexbus/src/__tests__/transcript.test.tspackages/hexbus/src/parser.tspackages/hexbus/src/runner.tspackages/hexbus/src/transcript.ts
**/*.{test,spec}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{test,spec}.{ts,tsx,js,jsx}: Write assertions insideit()ortest()blocks
Avoid done callbacks in async tests - use async/await instead
Don't use.onlyor.skipin committed code
Keep test suites reasonably flat - avoid excessivedescribenesting
Files:
packages/hexbus/src/__tests__/transcript.test.ts
🔇 Additional comments (3)
packages/hexbus/src/parser.ts (1)
33-45: LGTM!Also applies to: 209-214
packages/hexbus/src/runner.ts (1)
516-529: LGTM!Also applies to: 801-810, 813-815, 846-850
packages/hexbus/src/__tests__/transcript.test.ts (1)
1-24: LGTM!
Capture CLI stdout and stderr with global transcript flags so downstream CLIs can persist human-readable or JSONL runs for agent review.