Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/fix-docker-start-exit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@perstack/tui-components": patch
"perstack": patch
---

Short-circuit selection TUI when expert key is provided to fix immediate exit in compiled binaries.

In Bun-compiled binaries, the first Ink `render()` call for the selection phase caused the event loop to exit before React effects could fire, resulting in `start` exiting silently with code 0. By returning the selection result directly when `initialExpertKey` is known, the Ink render is skipped entirely.
18 changes: 4 additions & 14 deletions packages/tui-components/src/selection/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ type SelectionAppProps = SelectionParams & {
export const SelectionApp = (props: SelectionAppProps) => {
const {
showHistory,
initialExpertKey,
initialCheckpoint,
configuredExperts,
recentExperts,
historyJobs,
Expand Down Expand Up @@ -80,16 +78,8 @@ export const SelectionApp = (props: SelectionAppProps) => {
}
}, [exitResult, onComplete, exit])

// If expert key is provided, complete immediately (never rendered anything)
useEffect(() => {
if (initialExpertKey) {
onComplete({
expertKey: initialExpertKey,
checkpoint: initialCheckpoint,
})
exit()
}
}, [initialExpertKey, initialCheckpoint, onComplete, exit])
// Note: initialExpertKey shortcut is handled in renderSelection() before
// Ink render is called. This component only renders for interactive selection.

// Handlers
const completeWithExpert = useCallback(
Expand Down Expand Up @@ -192,8 +182,8 @@ export const SelectionApp = (props: SelectionAppProps) => {
],
)

// After exitResult is set or initialExpertKey shortcut, render null
if (exitResult || initialExpertKey) {
// After exitResult is set, render null so Ink's final frame is empty
if (exitResult) {
return null
}

Expand Down
12 changes: 12 additions & 0 deletions packages/tui-components/src/selection/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,20 @@ import type { SelectionParams, SelectionResult } from "./types.js"
/**
* Renders the selection TUI phase.
* Returns a promise that resolves with the selection result (expert and checkpoint).
*
* When `initialExpertKey` is provided, returns immediately without rendering the TUI.
* This avoids Ink lifecycle issues in compiled binaries where React effects may not
* fire before the process exits.
*/
export async function renderSelection(params: SelectionParams): Promise<SelectionResult> {
// Short-circuit: if expert key is already known, skip the TUI entirely
if (params.initialExpertKey) {
return {
expertKey: params.initialExpertKey,
checkpoint: params.initialCheckpoint,
}
}

return new Promise((resolve, reject) => {
let selectionResult: SelectionResult | undefined

Expand Down