Skip to content

fix(tui): stop swallowing tmux errors in startTuiTmuxServer - #1673

Merged
namastex888 merged 2 commits into
devfrom
fix/tui-startup-resilience
May 6, 2026
Merged

namastex888 merged 2 commits into
devfrom
fix/tui-startup-resilience

Conversation

@automagik-genie

@automagik-genie automagik-genie commented May 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Restructure startTuiTmuxServer so new-session -d -s genie-tui is only reachable when no session exists or after a recovery kill-session
  • Capture tmux stderr (drop stdio: 'ignore' on new-session / split-window) so users see duplicate session: genie-tui instead of opaque output: [ null, null, null ]
  • Recover from corrupt-session states (inner repair branch throws) by appending the original error to ~/.genie/logs/tui-crash.log (prefix [startTuiTmuxServer] <ISO> to disambiguate from sendTuiLaunchScript's native-panic appends), running kill-session, then freshCreateTuiSession()
  • Export startTuiTmuxServer as a test seam; add a 4-case regression suite locking in every reachable state

Original bug

With a stale genie-tui tmux session present, genie (no args) crashed with:

error: Command failed: tmux -L genie-tui ... new-session -d -s genie-tui ...
 status: 1, output: [ null, null, null ]
  at startTuiTmuxServer (genie.js:2616:221)
  at ensureTuiSession (genie.js:2620:1123)

Root cause: a single try { has-session; repair } catch {} block swallowed every inner failure (split-window, applyTuiStyle, etc.) and fell through to new-session, which collided with the still-existing session. stdio: 'ignore' hid tmux's duplicate session: genie-tui stderr, leaving Bun's execSync shim to emit the opaque output: [null, null, null].

Reproduced directly:

$ tmux -L genie-tui -f tui-tmux.conf new-session -d -s genie-tui -x 120 -y 40
duplicate session: genie-tui
$ echo $?
1

Approach

  1. Probe in its own try/catch — has-session is now the only signal that decides "fresh create" vs "repair". Non-existence is the only path into new-session.
  2. Repair branch in its own try — list-panes / split-window / styling. Throws here mean the session is in a state we can't reason about, so we log + kill + rebuild rather than bubbling an opaque error.
  3. runTuiTmuxCapturing helper — wraps execSync for the failure-prone calls with stdio: ['ignore', 'pipe', 'pipe'], re-throws as Error('tmux <cmd>: <stderr>') so the user sees tmux's actual complaint.
  4. freshCreateTuiSession helper — extracts the new-session / split-window / list-panes / style / keybindings flow, used both for first-time creation and post-kill recovery.
  5. logTuiStartupFailure — best-effort append to ~/.genie/logs/tui-crash.log, never throws.

kill-session recovery is safe: the genie-tui session is display-only (sendTuiLaunchScript rewrites both panes from scratch on every TUI entry), no work is lost.

Test plan

  • bun test src/term-commands/serve.test.ts — 15 pass / 0 fail (4 new startTuiTmuxServer cases plus the existing suite)
  • bun run typecheck clean
  • bun build src/genie.ts ... succeeds
  • Diff scope contained: only serve.ts + serve.test.ts + CHANGELOG.md
  • grep -n "new-session -d -s" src/term-commands/serve.ts shows exactly one occurrence (in freshCreateTuiSession)
  • Real-tmux smoke deferred — kill-server genie-tui is destructive to any active TUI on the host; the unit tests cover all four reachable states. Reviewer can run the smoke locally with the steps in the original wish.

Test cases

  • (a) session exists with 2 panes → returns early; new-session never called
  • (b) session exists with 1 pane → split-window called once; no new-session
  • (c) has-session fails → new-session + split-window each called once
  • (d) repair branch throws → kill-session + fresh new-session; ~/.genie/logs/tui-crash.log line contains [startTuiTmuxServer] prefix and the original tmux stderr

Wish

Workspace-local: genie-tui-startup-resilience. Two execution groups (G1 = code fix + stderr capture, G2 = test + PR), both green.

🤖 Generated with Claude Code

Summary by CodeRabbit

Bug Fixes

  • Enhanced Terminal UI startup reliability with improved session management and robust recovery mechanisms for consistent TUI initialization
  • Implemented comprehensive crash logging to capture Terminal UI startup diagnostics and simplify troubleshooting of initialization failures
  • Strengthened terminal session handling and repair logic to gracefully manage edge cases and unexpected connection interruptions

@coderabbitai

coderabbitai Bot commented May 6, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 291 files, which is 141 over the limit of 150.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 54e95cbe-98a8-42c4-ba80-63300118d7c1

📥 Commits

Reviewing files that changed from the base of the PR and between ab822bd and bbf0982.

⛔ Files ignored due to path filters (9)
  • .genie/assets/commits-30d.svg is excluded by !**/*.svg
  • .genie/assets/loc-30d.svg is excluded by !**/*.svg
  • .genie/assets/releases-30d.svg is excluded by !**/*.svg
  • CHANGELOG.md is excluded by !*.md
  • CLAUDE.md is excluded by !*.md
  • README.md is excluded by !*.md
  • VELOCITY.md is excluded by !*.md
  • audit-default-agents.md is excluded by !*.md
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (291)
  • .claude-plugin/marketplace.json
  • .docs-vendor
  • .genie/agents/metrics-updater/daily-stats.jsonl
  • .genie/agents/metrics-updater/runs.jsonl
  • .genie/agents/metrics-updater/state.json
  • .genie/brainstorm.md
  • .genie/brainstorms/hookify-perf-foundation/DESIGN.md
  • .genie/brainstorms/hookify-perf-foundation/DRAFT.md
  • .genie/brainstorms/hookify-third-party-absorption/DESIGN.md
  • .genie/brainstorms/hookify-third-party-absorption/DRAFT.md
  • .genie/brainstorms/v5-major-cutover-handoff/DESIGN.md
  • .genie/brainstorms/v5-major-cutover-handoff/DRAFT.md
  • .genie/wishes/agent-observability-snapshot/REPORT.md
  • .genie/wishes/agent-observability-snapshot/WISH.md
  • .genie/wishes/agent-yaml-permissions-wireup/WISH.md
  • .genie/wishes/canonical-genie-omni-wiring/WISH.md
  • .genie/wishes/complexity-budget-simplification/REPORT.md
  • .genie/wishes/complexity-budget-simplification/WISH.md
  • .genie/wishes/complexity-budget-simplification/complexity-baseline.md
  • .genie/wishes/complexity-budget-simplification/hotspots.md
  • .genie/wishes/dep-hygiene-and-resilience/STATUS.md
  • .genie/wishes/dep-hygiene-and-resilience/WISH.md
  • .genie/wishes/dep-hygiene-and-resilience/binary-sha-bootstrap.md
  • .genie/wishes/distribution-exodus/WISH.md
  • .genie/wishes/fix-agent-session-linkage/REPORT.md
  • .genie/wishes/fix-agent-session-linkage/WISH.md
  • .genie/wishes/fix-genie-work-phantom-dispatch/WISH.md
  • .genie/wishes/fix-next-tag-commit-pinning/WISH.md
  • .genie/wishes/fix-spawn-pipeline-silent-fail/WISH.md
  • .genie/wishes/fix-wish-parser-slug-prefix/WISH.md
  • .genie/wishes/fix-wish-parser-slug-prefix/scripts/smoke-1406.sh
  • .genie/wishes/fix-wish-task-tree-unification/WISH.md
  • .genie/wishes/genie-command-telemetry-boundary/WISH.md
  • .genie/wishes/genie-host-migrations/WISH.md
  • .genie/wishes/hookify-perf-foundation/REPORT.md
  • .genie/wishes/hookify-perf-foundation/WISH.md
  • .genie/wishes/hookify-perf-foundation/loadgen-baseline-pss.ts
  • .genie/wishes/hookify-perf-foundation/postgresql.conf.patch
  • .genie/wishes/hookify-perf-foundation/profile-baseline-pss-synthetic.json
  • .genie/wishes/hookify-perf-foundation/profile-sample-30s.json
  • .genie/wishes/hookify-third-party-absorption/WISH.md
  • .genie/wishes/observability-signal-normalization/OPERATOR-NOTES.md
  • .genie/wishes/observability-signal-normalization/REPORT.md
  • .genie/wishes/observability-signal-normalization/WISH.md
  • .genie/wishes/omni-activity-heartbeat/WISH.md
  • .genie/wishes/omni-activity-heartbeat/docs-delta.md
  • .genie/wishes/omni-host-fingerprint-trust/WISH.md
  • .genie/wishes/opentui-0.2-deep.md
  • .genie/wishes/opentui-0.2-deep.status.md
  • .genie/wishes/output-primitives-unified/SHARED-DESIGN.md
  • .genie/wishes/output-primitives-unified/WISH.md
  • .genie/wishes/pg-ci-v2-socket-leverage/WISH.md
  • .genie/wishes/pgserve-host-signed-identity/WISH.md
  • .genie/wishes/pgserve-singleton-no-proxy/SHARED-DESIGN.md
  • .genie/wishes/pgserve-singleton-no-proxy/WISH.md
  • .genie/wishes/smoke-test/WISH.md
  • .genie/wishes/task-create-wish-flag/WISH.md
  • .genie/wishes/tui-sidebar-canonical-visibility/WISH.md
  • .genie/wishes/update-unify-stages/SHARED-DESIGN.md
  • .genie/wishes/update-unify-stages/WISH.md
  • .genie/wishes/v5-major-cutover-handoff/WISH.md
  • .github/workflows/audit-next-tag.yml
  • .github/workflows/binary-sha-drift.yml
  • .github/workflows/ci.yml
  • .github/workflows/version.yml
  • .gitignore
  • .markdownlint-cli2.jsonc
  • .markdownlint.json
  • biome.json
  • install.sh
  • knip.json
  • package.json
  • packages/genie-app/src-backend/index.ts
  • packages/genie-app/src-backend/pg-bridge.ts
  • packages/genie-app/views/scheduler/ui/SchedulerView.tsx
  • packages/genie-app/views/sessions/ui/SessionsView.tsx
  • packages/watchdog/README.md
  • plugins/genie/.claude-plugin/plugin.json
  • plugins/genie/.orphaned_at
  • plugins/genie/agents/reviewer/AGENTS.md
  • plugins/genie/package.json
  • scripts/audit-next-tag-pinning.sh
  • scripts/complexity-budget.test.ts
  • scripts/complexity-budget.ts
  • scripts/list-tests.test.ts
  • scripts/list-tests.ts
  • scripts/pgserve-apply-tuning.ts
  • scripts/pgserve-profile.ts
  • scripts/postinstall-hook-binary.js
  • scripts/postinstall-migrations.js
  • scripts/postinstall-tmux.js
  • scripts/reconcile-orphans.test.ts
  • scripts/test-parallel.ts
  • scripts/tmux/tui-tmux.conf
  • skills/omni/SKILL.md
  • skills/report/SKILL.md
  • skills/trace/SKILL.md
  • src/__tests__/interactivity.test.ts
  • src/__tests__/resume.test.ts
  • src/__tests__/state-machine.invariants.test.ts
  • src/__tests__/tmux-config.test.ts
  • src/__tests__/tui-spawn-dx.integration.test.ts
  • src/db/migrations/044_phase_b_flip_defaults.test.ts
  • src/db/migrations/054_mailbox_source_meta.sql
  • src/db/migrations/055_runtime_events_partition_drain.sql
  • src/db/migrations/056_hook_perf_baseline_view.sql
  • src/db/migrations/057_hook_perf_baseline_view_filter_fix.sql
  • src/db/migrations/058_claude_usage_view.sql
  • src/db/migrations/059_agent_observability_view.sql
  • src/db/migrations/060_agent_observability_core_view.sql
  • src/db/migrations/061_agents_id_invariant_and_fk_lockdown.sql
  • src/db/migrations/061_agents_id_invariant_and_fk_lockdown.test.ts
  • src/db/migrations/062_drop_fk_mailbox_from_worker_temp.sql
  • src/db/migrations/063_readd_fk_mailbox_from_worker.sql.gated
  • src/db/migrations/agents-kind.test.ts
  • src/db/migrations/archive-legacy-identity-rows.test.ts
  • src/db/migrations/hook-perf-baseline.test.ts
  • src/db/migrations/master-backfill-and-shadow-cleanup.test.ts
  • src/db/migrations/observability-migrations.test.ts
  • src/detectors/__tests__/pattern-7-dispatch-silent-drop.broadcast-fanout.test.ts
  • src/detectors/pattern-7-dispatch-silent-drop.ts
  • src/detectors/shared/__tests__/team-leads.test.ts
  • src/genie-commands/__tests__/install.test.ts
  • src/genie-commands/__tests__/legacy-cleanup.test.ts
  • src/genie-commands/__tests__/session.test.ts
  • src/genie-commands/__tests__/update.test.ts
  • src/genie-commands/doctor.test.ts
  • src/genie-commands/doctor.ts
  • src/genie-commands/install.ts
  • src/genie-commands/legacy-cleanup.ts
  • src/genie-commands/migrate.ts
  • src/genie-commands/perf-check.ts
  • src/genie-commands/session.ts
  • src/genie-commands/update.ts
  • src/genie.ts
  • src/hooks/__tests__/dispatch.test.ts
  • src/hooks/__tests__/inject.test.ts
  • src/hooks/__tests__/loader.test.ts
  • src/hooks/__tests__/redaction.test.ts
  • src/hooks/__tests__/registry.test.ts
  • src/hooks/__tests__/resolve-agent-name.test.ts
  • src/hooks/__tests__/session-sync.test.ts
  • src/hooks/__tests__/trust.test.ts
  • src/hooks/codex-inject.test.ts
  • src/hooks/codex-inject.ts
  • src/hooks/define-hook.ts
  • src/hooks/dispatch-client.ts
  • src/hooks/dispatch-command.ts
  • src/hooks/env-identity.ts
  • src/hooks/handlers/__tests__/codex-inbox-deliver.test.ts
  • src/hooks/handlers/auto-spawn.ts
  • src/hooks/handlers/codex-inbox-deliver.ts
  • src/hooks/handlers/freshness.ts
  • src/hooks/handlers/identity-inject.ts
  • src/hooks/handlers/session-sync.ts
  • src/hooks/index.ts
  • src/hooks/inject.ts
  • src/hooks/loader.ts
  • src/hooks/redaction.ts
  • src/hooks/resolve-agent-name.ts
  • src/hooks/trust.ts
  • src/hooks/types.ts
  • src/lib/__tests__/auto-resume-zombie-cap.test.ts
  • src/lib/__tests__/edge-cases-stability.test.ts
  • src/lib/__tests__/mailbox.test.ts
  • src/lib/__tests__/spawn-single-row.test.ts
  • src/lib/__tests__/transport-aware-liveness.test.ts
  • src/lib/__tests__/zombie-spawns.test.ts
  • src/lib/agent-directory.test.ts
  • src/lib/agent-directory.ts
  • src/lib/agent-identity-sync.test.ts
  • src/lib/agent-observability.test.ts
  • src/lib/agent-observability.ts
  • src/lib/agent-registry.test.ts
  • src/lib/agent-registry.ts
  • src/lib/agent-yaml.test.ts
  • src/lib/agent-yaml.ts
  • src/lib/archive-zombies.test.ts
  • src/lib/audit-events.ts
  • src/lib/audit.test.ts
  • src/lib/audit.ts
  • src/lib/brain-vaults.test.ts
  • src/lib/brain-vaults.ts
  • src/lib/brief.test.ts
  • src/lib/channel-envelope.test.ts
  • src/lib/channel-envelope.ts
  • src/lib/claude-logs.test.ts
  • src/lib/claude-native-teams.test.ts
  • src/lib/claude-native-teams.ts
  • src/lib/db-backup.test.ts
  • src/lib/db-backup.ts
  • src/lib/db.test.ts
  • src/lib/db.ts
  • src/lib/events/schemas/hook.delivery.test.ts
  • src/lib/events/schemas/hook.delivery.ts
  • src/lib/executor-read.ts
  • src/lib/executor-registry.test.ts
  • src/lib/executor-registry.ts
  • src/lib/mailbox.ts
  • src/lib/omni-registration.ts
  • src/lib/omni-signature.test.ts
  • src/lib/omni-signature.ts
  • src/lib/otel-receiver.test.ts
  • src/lib/otel-receiver.ts
  • src/lib/pg-seed.test.ts
  • src/lib/pg-seed.ts
  • src/lib/protocol-router-spawn.test.ts
  • src/lib/protocol-router-spawn.ts
  • src/lib/protocol-router.test.ts
  • src/lib/protocol-router.ts
  • src/lib/provider-adapters.test.ts
  • src/lib/provider-adapters.ts
  • src/lib/provider-models.test.ts
  • src/lib/provider-models.ts
  • src/lib/providers/claude-code.test.ts
  • src/lib/providers/codex.ts
  • src/lib/scheduler-daemon.boot-pass.test.ts
  • src/lib/scheduler-daemon.test.ts
  • src/lib/scheduler-daemon.ts
  • src/lib/session-backfill.ts
  • src/lib/session-capture.test.ts
  • src/lib/session-capture.ts
  • src/lib/session-filewatch.test.ts
  • src/lib/session-filewatch.ts
  • src/lib/session-link-repair.ts
  • src/lib/should-resume.test.ts
  • src/lib/target-resolver.ts
  • src/lib/task-service.test.ts
  • src/lib/task-service.ts
  • src/lib/team-auto-spawn.ts
  • src/lib/team-chat.test.ts
  • src/lib/team-manager.test.ts
  • src/lib/team-manager.ts
  • src/lib/test-db.ts
  • src/lib/test-setup.test.ts
  • src/lib/test-setup.ts
  • src/lib/tui-disable.ts
  • src/lib/turn-close.test.ts
  • src/lib/unified-log.test.ts
  • src/lib/v1-migration-prompt.ts
  • src/lib/version.test.ts
  • src/lib/version.ts
  • src/lib/wish-state.ts
  • src/lib/workspace.test.ts
  • src/lib/workspace.ts
  • src/migrations/README.md
  • src/migrations/__tests__/orchestrator.test.ts
  • src/migrations/discover.ts
  • src/migrations/index.ts
  • src/migrations/runner.ts
  • src/migrations/steps/001-pm2-env-databaseurl-bake.ts
  • src/migrations/steps/002-kill-embedded-pgserve-legacy.ts
  • src/migrations/store.ts
  • src/serve/__tests__/hook-socket-loader.test.ts
  • src/serve/__tests__/hook-socket.test.ts
  • src/serve/hook-socket.ts
  • src/services/__tests__/agent-heartbeat.test.ts
  • src/services/__tests__/heartbeat-e2e.test.ts
  • src/services/__tests__/omni-bridge.test.ts
  • src/services/agent-heartbeat.ts
  • src/services/executor.ts
  • src/services/executors/__tests__/sdk-session-capture.test.ts
  • src/services/executors/claude-code.test.ts
  • src/services/executors/claude-code.ts
  • src/services/executors/claude-sdk.ts
  • src/services/executors/sdk-session-capture.ts
  • src/services/omni-bridge.ts
  • src/term-commands/__tests__/agents-resume.test.ts
  • src/term-commands/__tests__/spawn-identity-name-guard.test.ts
  • src/term-commands/agent/inbox.test.ts
  • src/term-commands/agent/inbox.ts
  • src/term-commands/agent/index.ts
  • src/term-commands/agent/observe.ts
  • src/term-commands/agent/register.ts
  • src/term-commands/agent/send.ts
  • src/term-commands/agent/show.ts
  • src/term-commands/agent/spawn.ts
  • src/term-commands/agents.spawn-autosync.test.ts
  • src/term-commands/agents.test.ts
  • src/term-commands/agents.ts
  • src/term-commands/audit-events.ts
  • src/term-commands/brain.test.ts
  • src/term-commands/brain.ts
  • src/term-commands/db-ls.ts
  • src/term-commands/db-migrate-v1.ts
  • src/term-commands/db.test.ts
  • src/term-commands/db.ts
  • src/term-commands/dir-add.test.ts
  • src/term-commands/dir.test.ts
  • src/term-commands/dir.ts
  • src/term-commands/dispatch.test.ts

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR enhances TUI startup resilience in the serve command by introducing robust tmux session management with repair/recovery logic, new error logging, and comprehensive test coverage with a custom per-test execution handler for execSync mocking.

Changes

TUI Startup Resilience & Test Coverage

Layer / File(s) Summary
Core Implementation
src/term-commands/serve.ts
New helper functions: runTuiTmuxCapturing() executes tmux commands with error capture; logTuiStartupFailure() writes diagnostics to ~/.genie/logs/tui-crash.log; freshCreateTuiSession() creates a new session with layout, styling, and keybindings. Session management logic rewritten to check for existing sessions, repair panes if needed, or recreate cleanly on failure. Import augmented with appendFileSync.
Public API Export
src/term-commands/serve.ts
startTuiTmuxServer() function exported (was internal); docstring notes it is exported for unit tests. Function now implements defensive session lifecycle with explicit repair and recreation pathways.
Test Infrastructure
src/term-commands/serve.test.ts
Bun mock introduced for node:child_process that preserves real bindings, routes execSync calls targeting genie-tui to a per-test handler, and selectively swallows spawnSync invocations to prevent real tmux interactions. Dynamic import of serve.js with destructured test utilities.
Test Coverage
src/term-commands/serve.test.ts
New startTuiTmuxServer test suite with recorder helper to simulate execSync responses and capture command history. Four regression tests: existing 2-pane session (validates early return), existing 1-pane session (validates split-window), has-session failure (validates fresh creation), and repair failure (validates kill-session → new-session and crash log contents).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main objective: preventing tmux errors from being swallowed in startTuiTmuxServer, which is the core problem this PR solves.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tui-startup-resilience

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request enhances the resilience of the TUI startup process by refactoring the startTuiTmuxServer logic. Key changes include separating the session probe from the repair logic, implementing detailed error capturing with runTuiTmuxCapturing, and adding a recovery path that logs failures to tui-crash.log before recreating corrupt sessions. Additionally, a robust mocking strategy was introduced in the test suite to verify these recovery scenarios. Feedback was provided to ensure that pane ID parsing is defensive against empty strings, maintaining consistency across the implementation.

Comment on lines +336 to +338
const panes = execSync(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`), { encoding: 'utf-8' })
.trim()
.split('\n');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The split('\n') call can result in an array containing an empty string if the command output has trailing newlines or is empty. Adding a filter for non-empty strings ensures that the resulting panes array contains only valid identifiers, maintaining consistency with the logic used in the repair branch at line 385.

Suggested change
const panes = execSync(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`), { encoding: 'utf-8' })
.trim()
.split('\n');
const panes = execSync(tuiTmux("list-panes -t " + TUI_SESSION + ":0 -F '#{pane_id}'"), { encoding: 'utf-8' })
.trim()
.split('\n')
.filter((id) => id.length > 0);

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/term-commands/serve.ts`:
- Around line 371-375: The catch around execSync(tuiTmux(`has-session -t
${TUI_SESSION}`)) treats all errors as "session missing" and wrongly calls
freshCreateTuiSession(); replace the execSync probe with
runTuiTmuxCapturing(tuiTmux(...)) and inspect the captured stderr/message to
only treat known tmux absence/connectivity errors (match with a regex such as
/can't find session|no server|failed to connect/i) as grounds to call
freshCreateTuiSession(), and rethrow any other unexpected errors so real
tmux/socket/permission issues surface; keep references to tuiTmux, TUI_SESSION,
freshCreateTuiSession, and runTuiTmuxCapturing to locate the change.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0d13bc12-7114-4aa9-bc45-0f4395776f5a

📥 Commits

Reviewing files that changed from the base of the PR and between ca0a1d8 and ab822bd.

⛔ Files ignored due to path filters (1)
  • CHANGELOG.md is excluded by !*.md
📒 Files selected for processing (2)
  • src/term-commands/serve.test.ts
  • src/term-commands/serve.ts

Comment on lines 371 to +375
try {
execSync(tuiTmux(`has-session -t ${TUI_SESSION}`), { stdio: 'ignore' });
// Session exists — reuse it, but ensure the split is healthy
} catch {
// Session genuinely doesn't exist — create it from scratch.
return freshCreateTuiSession();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

❓ Verification inconclusive

What stderr/exit behavior does tmux has-session -t <name> use when (1) the session is missing, (2) no server is running on the socket, and (3) the probe fails for other reasons such as socket/config/permission errors?


tmux has-session -t <name> 1) Session is missing - Exit status: 1. - stderr: an error message indicating the session was not found (for example, “can’t find session ”). [1][2] 2) No server is running on the socket - Exit status: tmux reports a “failed to connect to server” / “no server running on …” type error, and returns a non-zero status (commonly 1). [3][4] - stderr: the “failed to connect to server …” / “no server running on …” message is printed to standard error. [3] 3) Probe fails for other reasons (socket/config/permission errors) - Exit status: non-zero (tmux cannot complete the target-session check). In practice, you’ll see a “failed to connect to server …” style error on stderr when the client can’t talk to the server (e.g., permissions/accessibility issues to the tmux socket). [5] - stderr: the connect/permission problem is surfaced as an error message on standard error (e.g., “failed to connect to server: Connection refused …”); tmux does not treat this as the “session missing” case. [5] Practical takeaway for scripts - Treat “exit code 0” as “session exists (and the probe succeeded)”. - Treat “exit code 1 or other non-zero” as “either session missing or the client couldn’t reach/probe the server/sockets (socket/config/permission problems).” [1][3][5]

Citations:


Script executed:

# Check if runTuiTmuxCapturing exists and its implementation
rg -A 10 "function runTuiTmuxCapturing|const runTuiTmuxCapturing" src/term-commands/serve.ts

Repository: automagik-dev/genie


Repository: automagik-dev/genie
Exit code: 0

stdout:

function runTuiTmuxCapturing(cmd: string): string {
  try {
    return execSync(tuiTmux(cmd), { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
  } catch (err) {
    const e = err as { stderr?: string | Buffer; stdout?: string | Buffer; message?: string };
    const stderr = typeof e.stderr === 'string' ? e.stderr : e.stderr?.toString('utf-8');
    const stdout = typeof e.stdout === 'string' ? e.stdout : e.stdout?.toString('utf-8');
    const detail = (stderr ?? stdout ?? e.message ?? 'unknown tmux error').trim();
    throw new Error(`tmux ${cmd}: ${detail}`);
  }
}


Script executed:

# Get line counts and check overall structure around the reviewed lines
wc -l src/term-commands/serve.ts
sed -n '280,290p' src/term-commands/serve.ts
sed -n '360,380p' src/term-commands/serve.ts

Repository: automagik-dev/genie


Repository: automagik-dev/genie
Exit code: 0

stdout:

1404 src/term-commands/serve.ts
/**
 * Run a tmux command via execSync and re-throw with the captured stderr
 * embedded in the message. Bun's execSync throws an `Error` whose default
 * message is the unhelpful `output: [null, null, null]` when stdio is
 * ignored — capturing stdout/stderr as strings gives us tmux's actual
 * complaint (e.g. `duplicate session: genie-tui`).
 */
function runTuiTmuxCapturing(cmd: string): string {
  try {
    return execSync(tuiTmux(cmd), { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
  } catch (err) {
 *      `new-session` and crashed with `duplicate session: genie-tui`.
 *   2. If the session exists, run the repair branch in its own try. On
 *      failure: log to ~/.genie/logs/tui-crash.log, kill the corrupt
 *      session, and rebuild from scratch — the user just typed `genie`
 *      and wants a working TUI.
 *
 * Exported so unit tests can exercise it directly without monkey-patching
 * `child_process` globally.
 */
export function startTuiTmuxServer(): { leftPane: string; rightPane: string } {
  // Step 1: probe — the ONLY signal that decides "fresh create" vs "repair".
  try {
    execSync(tuiTmux(`has-session -t ${TUI_SESSION}`), { stdio: 'ignore' });
  } catch {
    // Session genuinely doesn't exist — create it from scratch.
    return freshCreateTuiSession();
  }

  // Step 2: session exists — try to repair / reuse. Any throw in this branch
  // means the session is in a bad state we can't reason about: log, kill,
  // and rebuild rather than bubbling an opaque error to the user.


Gate the fallback on actual tmux error messages, not a blanket catch.

This catch silently treats all has-session failures as "session missing". If the probe fails for socket/connection/permission issues, you incorrectly call freshCreateTuiSession() instead of surfacing the real tmux error.

runTuiTmuxCapturing is already defined in this file and captures stderr. Switch to it and inspect the error message to distinguish acceptable failures (session missing, server absent, connection lost) from unexpected ones.

The regex pattern in the suggested fix should also cover "failed to connect to server" messages; refine it to /can't find session|no server|failed to connect/i or similar to handle all acceptable cases before rethrowing unexpected errors.

🧰 Tools
🪛 OpenGrep (1.20.0)

[ERROR] 372-372: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/term-commands/serve.ts` around lines 371 - 375, The catch around
execSync(tuiTmux(`has-session -t ${TUI_SESSION}`)) treats all errors as "session
missing" and wrongly calls freshCreateTuiSession(); replace the execSync probe
with runTuiTmuxCapturing(tuiTmux(...)) and inspect the captured stderr/message
to only treat known tmux absence/connectivity errors (match with a regex such as
/can't find session|no server|failed to connect/i) as grounds to call
freshCreateTuiSession(), and rethrow any other unexpected errors so real
tmux/socket/permission issues surface; keep references to tuiTmux, TUI_SESSION,
freshCreateTuiSession, and runTuiTmuxCapturing to locate the change.

vasconceloscezar and others added 2 commits May 6, 2026 17:02
The bare `catch {}` after `has-session` previously swallowed every error
from the repair branch and fell through to `new-session -d -s genie-tui`,
which then failed with `duplicate session: genie-tui` whenever the
session existed but had an unexpected layout. Combined with
`stdio: 'ignore'`, the user saw an opaque `output: [null, null, null]`
traceback from Bun's execSync shim.

This commit:

- Splits the `has-session` probe from the repair branch so `new-session`
  is reachable ONLY when the session genuinely doesn't exist.
- On repair-branch failure, logs the original error to
  `~/.genie/logs/tui-crash.log` (prefixed `[startTuiTmuxServer] <ISO>`),
  kills the corrupt session, and rebuilds via `freshCreateTuiSession`.
- Replaces `stdio: 'ignore'` on `new-session`/`split-window` with
  captured stdout/stderr; on failure re-throws an `Error` whose message
  embeds tmux's actual stderr (e.g. `duplicate session: genie-tui`).
- Extracts the fresh-create steps into a dedicated helper to remove the
  duplication between the new and recovery paths.
- Exports `startTuiTmuxServer` so the G2 unit test can drive it directly
  without monkey-patching `child_process` globally.

Wish: genie-tui-startup-resilience (G1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four cases covering every reachable state of the function:

  (a) session exists, 2 panes  → returns early; new-session never called
  (b) session exists, 1 pane   → split-window once; no new-session
  (c) has-session fails        → fresh new-session + split-window
  (d) repair branch throws     → kill-session + fresh recreate;
                                 ~/.genie/logs/tui-crash.log gets a
                                 [startTuiTmuxServer] <ISO> line containing
                                 the original tmux stderr

The bug being locked in: a single bare catch {} used to swallow every
inner-branch failure and fall through to new-session -d -s genie-tui,
which then collided with the still-existing session and crashed with
"duplicate session: genie-tui" surfaced as opaque "output: [null,null,null]"
through Bun's execSync shim.

Mocking: file-level mock.module('node:child_process', ...) installs a
per-test command handler that intercepts only -L genie-tui invocations
and lets every other execSync call (pgserve setup, etc.) hit the real
child_process module. GENIE_HOME points to a tmpdir for case (d) so the
crash-log assertion is hermetic.

Wish: genie-tui-startup-resilience
@automagik-genie
automagik-genie force-pushed the fix/tui-startup-resilience branch from ab822bd to bbf0982 Compare May 6, 2026 20:10
@automagik-genie
automagik-genie changed the base branch from main to dev May 6, 2026 20:10
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​opentui/​keymap@​0.2.0801009495100
Addednpm/​chokidar@​5.0.010010010081100
Updatednpm/​@​opentui/​core@​0.1.103 ⏵ 0.2.09410092 +198100
Updatednpm/​@​opentui/​react@​0.1.103 ⏵ 0.2.098100100 +198100

View full report

@namastex888
namastex888 merged commit 113b0b1 into dev May 6, 2026
20 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request May 10, 2026
@automagik-genie
automagik-genie deleted the fix/tui-startup-resilience branch September 25, 2026 04:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants