feat: Add initialCommand parameter to TabbedTerminal#122
Conversation
Code Review: Add initialCommand ParameterOverviewThis PR adds the ability to execute a command automatically when a terminal tab is created. The implementation is straightforward and follows existing patterns in the codebase. Positives
Issues and Concerns1. Hardcoded Delay - Critical The 500ms delay is arbitrary and may not work reliably across different shell types, system loads, or shell configurations. Command may execute before shell is ready, leading to lost or ignored commands. Recommendation: Consider using OSC 133 integration (which already exists in the codebase) to detect prompt readiness, or make the delay configurable. 2. Error Handling Missing The write call can throw IOException if the PTY is closed prematurely or process exits during delay. No try-catch present. Recommendation: Add error handling and check handle.isConnected before writing. 3. Command Injection Risk - Security No validation or sanitization of initialCommand. Malicious input could execute multiple commands. Recommendation: Document security implications clearly in KDoc. Add WARNING that this accepts arbitrary shell syntax and should only be used with trusted input sources. 4. Limited to First Tab Only The parameter is only passed when creating the first tab. Subsequent tabs via Ctrl+T cannot use this feature. Question: Is this intentional? Should this be documented? 5. Documentation Style The duplicate KDoc comment blocks should be merged. Testing RecommendationsExpand coverage to include: commands that fail, empty string, special characters, tab closing during delay, different shells. PerformanceMinor: Every terminal startup has a 500ms coroutine overhead even when initialCommand is null. Overall AssessmentVerdict: Good feature addition with some important concerns to address Priority Fixes:
Questions:
|
Add support for passing an initial command to run when a terminal tab is first created. This enables embedding applications (like BOSS) to open a terminal and automatically execute a specific command. Changes: - Add `initialCommand` parameter to TabbedTerminal composable - Add `initialCommand` parameter to TabController.createTab() - Send initial command to PTY after shell is ready (500ms delay) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
9a17f3c to
7caa082
Compare
Code Review: Initial Command FeatureThank you for this contribution! This is a useful feature for embedding applications. I've reviewed the changes and have several suggestions for improvement. ✅ Strengths
|
| Category | Finding | Severity |
|---|---|---|
| Race Condition | Hardcoded 500ms delay | High |
| Error Handling | Missing try-catch and liveness check | High |
| Lifecycle | Coroutine scope not tied to tab | Medium |
| Security | Potential command injection | Medium |
| Testing | No automated tests | Low |
| Documentation | Clarify first-tab-only behavior | Low |
✅ Approval Recommendation
Conditional Approval - Please address items #1-3 before merging. This is a solid foundation, but the timing/error handling issues could cause problems in production.
Great work on a useful feature! Let me know if you'd like help implementing any of these suggestions.
🤖 Generated with Claude Code
Add a configurable setting to run a command automatically when new terminal tabs are created. This complements the programmatic initialCommand parameter added in the previous commit. Changes: - Add `initialCommand` setting to TerminalSettings (default: empty) - Add "Initial Command" text field in Behavior > Shell settings - Wire up setting to all createTab() calls in TabbedTerminal - Priority: programmatic parameter > setting > none Use case: Users can configure commands like "neofetch", "cd ~/projects", or custom startup scripts to run automatically in every new tab. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Code Review: Initial Command SupportSummaryThis PR adds support for running commands automatically when a terminal tab starts. The implementation is clean and follows the existing codebase patterns well. ✅ Strengths
🔴 Critical Issues1. Race Condition with Shell Initialization (Security & Reliability)Location: // Current implementation
if (initialCommand != null) {
launch(Dispatchers.IO) {
delay(500) // ⚠️ HARD-CODED DELAY
handle.write(initialCommand + "\n")
}
}Problems:
Security Concern: If the shell is not ready, the command could be executed in an unpredictable state or worse, be interpreted as part of shell initialization, potentially allowing command injection through shell startup files. Recommended Solution: Use OSC sequences for shell readiness detection (similar to OSC 7 for working directory tracking): // Add to BossTerminal.kt or create InitialCommandListener.kt
interface ShellReadyListener {
fun onShellReady()
}
// In shell setup (e.g., ~/.bashrc or ~/.zshrc), emit:
// precmd() { echo -ne "\\033]133;A\\007" } # Zsh
// PROMPT_COMMAND='echo -ne "\\033]133;A\\007"' # Bash
// In TabController, listen for OSC 133;A (prompt ready) before sending commandAlternative (if OSC approach is too complex): At minimum, increase delay to 1000ms and add a comment explaining the trade-off. 2. Priority Logic Issue with SettingsLocation: // Priority: parameter > settings > none
val effectiveInitialCommand = initialCommand ?: settings.initialCommand.ifEmpty { null }
tabController.createTab(initialCommand = effectiveInitialCommand)Problem: The priority logic doesn't match the comment or expected behavior:
Expected Behavior:
Fix: // Three-way priority: explicit parameter > settings > none
val effectiveInitialCommand = when {
initialCommand != null -> initialCommand.ifEmpty { null } // Explicit override (including empty)
else -> settings.initialCommand.ifEmpty { null } // Fall back to settings
}
|
Add verticalScroll to NavigationRail so categories can be scrolled when the window is too small to show all items. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Code Review: Initial Command SupportThis PR adds the ability to execute an initial command when a terminal tab is created, both programmatically and via user settings. Overall, this is a well-structured implementation that follows the existing codebase patterns. Here's my detailed feedback: ✅ Strengths
🐛 Potential Issues1. Timing Issue: 500ms delay is unreliable (TabController.kt:1042)delay(500)
handle.write(initialCommand + "\n")Problem: The 500ms delay is a hardcoded guess that may be too short or too long depending on:
Impact: Commands may execute before the shell is ready, causing them to be lost or partially executed. Recommendation: Implement shell-ready detection: // Option 1: Wait for OSC 133;A (prompt ready) signal
// This requires shell integration but is most reliable
// Option 2: Wait for first prompt character detection
// Less reliable but doesn't require shell integration
// Option 3: Increase delay to 1500ms as a safer default
delay(1500)2. Security: No input sanitizationProblem: The
Current Risk: LOW (user-controlled settings are generally trusted) Recommendation: Add security guidance in documentation: /**
* @param initialCommand Optional command to run in the first terminal tab after startup.
* SECURITY: This string is sent directly to the shell without escaping.
* Ensure input is validated if sourced from untrusted origins.
*/3. Error Handling: Silent failuresProblem: If Recommendation: launch(Dispatchers.IO) {
delay(500)
try {
handle.write(initialCommand + "\n")
} catch (e: Exception) {
println("WARNING: Failed to send initial command: ${e.message}")
// Consider showing notification to user
}
}💡 Design Considerations4. Missing: Per-tab initial commandsCurrent: All new tabs created via Cmd+T use the settings-based initial command Consideration: Is this intentional? For example, users might want:
This is likely out of scope for this PR, but worth noting for future enhancement. 5. Missing: Command history integrationObservation: The initial command is sent as raw input, so it appears in shell history. Question: Is this the desired behavior? Some users might prefer initial commands to be invisible in history (similar to 📝 Code Quality6. Redundant code pattern (TabbedTerminal.kt)The pattern appears 3 times: tabController.createTab(initialCommand = settings.initialCommand.ifEmpty { null })Suggestion: Extract to helper function: private fun TerminalSettings.getInitialCommandOrNull() =
initialCommand.ifEmpty { null }
// Usage:
tabController.createTab(initialCommand = settings.getInitialCommandOrNull())Or better, handle this logic inside fun createTab(
// ...
initialCommand: String? = null
): TerminalTab {
val effectiveCommand = initialCommand?.ifEmpty { null }
// ...
}7. Missing: Settings validationThe settings UI accepts any string, including:
Recommendation: Add basic validation or clarify expected format in UI description. 🧪 Testing RecommendationsBeyond the test plan in the PR description, consider testing:
📊 PerformanceImpact: Negligible. The delay happens in a background coroutine and doesn't block UI or other tabs. 🔒 Security AssessmentOverall: LOW RISK for current use case (user settings) Concerns for future embedding:
✅ SummaryThis is a solid implementation with good adherence to project conventions. The main concern is the unreliable 500ms timing which could cause issues for users with slow-starting shells. I recommend:
The feature itself is well-designed and will be useful for users who want custom startup commands. 🤖 Review generated with Claude Code |
The LaunchedEffect that wires up menu callbacks (onSplitVertical, onSplitHorizontal) only had keys (menuActions, activeTabIndex). It ran before any tabs existed and returned early. When the first tab was created, activeTabIndex stayed at 0, so the effect never re-ran to set up the callbacks. Added tabController.tabs.size to the LaunchedEffect keys so it re-runs when tabs are added, ensuring menu actions are properly wired up. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Code Review - PR #122: Add initialCommand ParameterI've reviewed this PR and have the following feedback: ✅ Strengths
|
| Category | Status | Notes |
|---|---|---|
| Functionality | ✅ Good | Clean API design, proper integration |
| Code Quality | ✅ Good | Consistent patterns, well-documented |
| Performance | Fixed 500ms delay is a race condition | |
| Security | Document command injection risk | |
| Testing | Add race condition and edge case tests | |
| UI/UX | Navigation rail scroll change unexplained |
🎯 Recommended Actions Before Merge
- MUST FIX: Replace fixed delay with OSC 133 prompt detection or make delay configurable
- SHOULD ADD: Security warning in initialCommand KDoc
- SHOULD CLARIFY: Explain or separate the navigation rail scroll change
- SHOULD TEST: Add test cases for race conditions and edge cases
💬 Questions for Author
- Have you tested this with slow shell initialization (e.g., Oh My Zsh with 20+ plugins)?
- Did you consider OSC 133;A detection for shell readiness?
- Is the navigation rail scroll change intentional?
Overall, this is a well-implemented feature with a clear use case. The main concern is the timing race condition, which could cause subtle bugs in real-world usage. With the OSC 133 detection or configurable delay, this would be production-ready.
Great work! 🚀
The initial command was sent after a hardcoded 500ms delay which was a brittle race condition - too short for slow shells, too long for fast ones, and varying by machine performance. Changes: - Use OSC 133;A (prompt started) signal to detect when shell is ready - Add configurable fallback delay (initialCommandDelayMs) for shells without OSC 133 integration (default: 500ms, range: 100-2000ms) - Add UI slider in Behavior Settings to configure the fallback delay - Clean up temporary listener after initial command is sent With OSC 133 shell integration configured, the initial command is sent immediately when the prompt appears. Without it, falls back to the configurable delay. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Code Review - PR #122: Add initialCommand parameter to TabbedTerminalThank you for this feature implementation! This is a well-thought-out addition that provides both programmatic API and user-facing configuration. Here's my detailed review: ✅ Strengths1. Excellent Architecture
2. Smart SynchronizationThe OSC 133 prompt detection is particularly clever - using CompletableDeferred with a temporary CommandStateListener avoids race conditions and ensures the shell is truly ready before sending commands. 3. Good Documentation
🔍 Issues & Concerns1. Critical: Race Condition on Terminal Disposal
|
Updated descriptions for Initial Command and Initial Command Delay settings to clarify they only affect newly created tabs. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Code Review: Initial Command FeatureSummaryThis PR adds the ability to execute a command automatically when a terminal tab is created, both programmatically (via API parameter) and through user settings. The implementation is well-structured and follows the project's conventions. ✅ Strengths
🔍 Issues & Recommendations1. Race Condition Risk (HIGH PRIORITY)Location: TabController.kt:1039-1075 The temporary Recommendation: // Check if terminal is already at prompt (in case OSC 133 fired before listener added)
if (tab.terminal.isAtPrompt()) {
promptReady.complete(Unit)
} else {
tab.terminal.addCommandStateListener(promptListener)
}Alternatively, restructure to add the listener before any terminal output processing begins. 2. No Input Validation (MEDIUM PRIORITY)Location: TerminalSettings.kt:162 The
Recommendation: val initialCommand: String = ""
get() = field.trim().takeIf { it.isNotEmpty() && !it.contains('\n') } ?: ""Or add UI-level validation in 3. Hardcoded Delay ValueLocation: TabController.kt:1063 The 50ms delay after OSC 133;A is hardcoded: delay(50) // Small delay to ensure prompt is fully renderedRecommendation:
4. Settings Validation MissingLocation: TerminalSettings.kt:171 The /**
* Range: 100-5000ms. Default: 500ms.
*/
val initialCommandDelayMs: Int = 500Recommendation: val initialCommandDelayMs: Int = 500
get() = field.coerceIn(100, 5000)Also update the slider in BehaviorSettingsSection.kt:42 to match: valueRange = 100f..5000f, // Currently 100f..2000f5. Error Handling AbsentLocation: TabController.kt:1069 The Recommendation: try {
handle.write(initialCommand + "\n")
} catch (e: Exception) {
println("WARN: Failed to send initial command: " + e.message)
}6. Potential Command Injection (LOW RISK)Location: TabController.kt:1069 While the terminal is already running arbitrary shell commands, the initial command is user-controlled input that's directly appended with Recommendation:
7. Test Coverage MissingThe PR description includes a test plan, but no automated tests are added. Recommendation:
🎯 Minor Suggestions
📊 Performance Considerations
🔒 Security Assessment
✅ Alignment with CLAUDE.mdThe PR follows project conventions:
VerdictStatus: The core implementation is solid and feature-complete. The main concern is the race condition risk in the OSC 133 listener timing. The other issues are improvements that enhance robustness but aren't blockers. Required Before Merge:
Recommended for Follow-up:
Great work on this feature! The OSC 133 integration is particularly clever. 🚀 |
When clicking on a split pane running a mouse-aware TUI app (like nvim), the click was being forwarded to the app without first focusing the pane. This made it impossible to switch focus to panes running such apps. Changes: - Add onPaneFocus callback to ProperTerminal - Call onPaneFocus at the start of press handler, before any other processing (including mouse event forwarding) - Remove redundant pointerInput handler from SplitContainer - Clean up unused imports Now clicking on any split pane will focus it first, then forward the mouse event to the terminal application if applicable. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Code Review: Initial Command FeatureGreat work on implementing this feature! The overall implementation is solid and follows the project's conventions well. Here are my detailed findings: ✅ Strengths
🐛 Potential Issues1. Race Condition: Listener Registration Timing (High Priority)File: The Problem: // PTY starts here (line ~974)
handle.start(tab.coroutineScope)
// ... many lines of setup code ...
// Listener registered much later (line 1053)
tab.terminal.addCommandStateListener(promptListener)Solution: Register the listener before starting the PTY: // Set up initial command listener BEFORE starting PTY
val promptReady = if (initialCommand \!= null) {
val deferred = CompletableDeferred<Unit>()
val listener = object : CommandStateListener {
override fun onPromptStarted() {
deferred.complete(Unit)
}
}
tab.terminal.addCommandStateListener(listener)
Pair(deferred, listener)
} else null
// NOW start the PTY
handle.start(tab.coroutineScope)
// ... later, in the existing launch block ...
if (promptReady \!= null) {
val (deferred, listener) = promptReady
try {
withTimeoutOrNull(settings.initialCommandDelayMs.toLong()) {
deferred.await()
}?.let { delay(50) }
handle.write(initialCommand\!\! + "\n")
} finally {
tab.terminal.removeCommandStateListener(listener)
}
}2. Uncaught Exception:
|
| Category | Rating | Notes |
|---|---|---|
| Code Quality | ⭐⭐⭐⭐ | Clean, well-structured, follows project conventions |
| Functionality | ⭐⭐⭐⭐⭐ | Feature-complete with excellent OSC 133 integration |
| Error Handling | ⭐⭐⭐ | Missing try-catch around handle.write() |
| Documentation | ⭐⭐⭐⭐ | Good inline docs, needs CLAUDE.md update |
| Test Coverage | ⭐⭐ | No unit tests for new functionality |
| Security | ⭐⭐⭐⭐ | Safe design, minor documentation improvements suggested |
🚀 Recommendation
APPROVE with minor fixes:
- Fix the race condition by registering listener before PTY start (HIGH PRIORITY)
- Add try-catch around
handle.write()(MEDIUM PRIORITY) - Consider adding unit tests (MEDIUM PRIORITY)
The core implementation is excellent. The OSC 133 integration is particularly well-done and aligns perfectly with the project's shell integration standards. Once the race condition is addressed, this is ready to merge.
🤖 Generated with Claude Code
Summary
initialCommandparameter toTabbedTerminalcomposable for programmatic useinitialCommandsetting in Settings > Behavior > Shell for user configurationTabController.createTab()Features
1. Programmatic API (for embedding)
Enables embedding applications (like BOSS) to open a terminal and automatically execute a specific command:
2. User Setting (for daily use)
Users can configure a startup command in Settings > Behavior > Shell:
neofetch,cd ~/projects,source ~/.profile && clearPriority
When both are set: programmatic parameter > user setting > none
Test plan
initialCommand = "echo test"parameter and verify command runstoporhtopecho helloand create new tab🤖 Generated with Claude Code