fix: prevent premature exit in non-interactive mode when tasks pending#217
Conversation
Detects non-interactive environments (CI, opencode run) and prevents session idle when: - Background tasks are still running - Incomplete todos remain in the queue Changes: - Add isNonInteractive() detector for CI/headless environment detection - Export detector from non-interactive-env hook module - Enhance todo-continuation-enforcer to inject prompts BEFORE session.idle - Pass BackgroundManager to todo-continuation-enforcer for task status checks This fix prevents `opencode run` from exiting prematurely when work is pending. 🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
|
All contributors have signed the CLA. Thank you! ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
|
@sisyphus-dev-ai review this |
|
👋 Hey @code-yeongyu! I'm on it... |
sisyphus-dev-ai
left a comment
There was a problem hiding this comment.
Review Summary
Excellent work on fixing the premature exit issue in non-interactive mode! The solution is elegant and addresses the root cause effectively. The implementation is solid with good error handling and state management.
✅ Strengths
-
Root Cause Analysis: Correctly identified that the 2-second countdown is too slow for
opencode runwhich exits immediately onsession.idle -
Smart Timing: Hooking into
message.updatedBEFOREsession.idlefires is the right approach - prevents the session from going idle in the first place -
Robust Detection:
detector.tscovers multiple cases:- CI environment variables (
CI,GITHUB_ACTIONS) - OpenCode-specific variables (
OPENCODE_RUN,OPENCODE_NON_INTERACTIVE) - TTY detection as fallback
- CI environment variables (
-
Proper State Management:
preemptivelyInjectedSessionsprevents duplicate injections- Cleared on user message and injection failure
- Respects recovery/error states
-
BackgroundManager Integration: Checks for running background tasks, not just todos - comprehensive solution
-
Error Handling: Gracefully handles failures and cleans up state
🤔 Questions & Suggestions
1. Injected Message Role Assumption
The code assumes injected prompts (line 346-353) are processed as "user" messages, which would trigger the clear logic at line 294. Could you verify this behavior? If the injected messages have a different role, preemptivelyInjectedSessions might not get cleared, preventing re-injection on subsequent finishes.
Test scenario:
- Agent finishes with incomplete todos → preemptive injection
- Agent processes prompt but finishes again without completing todos
- Does second injection happen?
2. Type Change Verification
Line 283: finish type changed from boolean | undefined to string | undefined. This aligns with the logic at line 301, but could you confirm this matches the actual event payload structure?
3. TTY Detection Edge Case
Line 14 in detector.ts:
if (!process.stdout.isTTY) {
return true
}This correctly handles undefined (when stdout is not a TTY), but consider making it explicit for clarity:
if (process.stdout.isTTY === false || process.stdout.isTTY === undefined) {
return true
}4. Consider Adding Tests
Given the complexity of the state management, consider adding test cases for:
- Multiple preemptive injections in sequence
- Background task completion during injection
- Error recovery flows
📝 Minor Suggestions
Export Consistency
non-interactive-env/index.ts now exports detector.ts. Consider whether this should be the default export or if the structure matches other hook modules.
Logging Verbosity
The extensive logging is great for debugging! Consider whether all logs should remain at the default level or if some could be moved to a debug level once the feature stabilizes.
🎯 Testing Validation
The PR description mentions:
- ✅
bun run typecheck- Pass - ✅
bun test- 31 pass, 0 fail
Recommendation: Add a real-world CI test by triggering a GitHub Actions workflow that:
- Runs
opencode runwith background tasks - Verifies the process doesn't exit prematurely
- Confirms all todos complete
Verdict
LGTM with minor questions ✅
The implementation is sound and solves a critical issue for CI/CD workflows. The questions above are mostly for verification and documentation purposes. Great work!
Closes #216 appropriately. Merge when ready!
Review ResponseThanks @sisyphus-dev-ai for the thorough review! Answers to Questions1. Injected Message Role ✅ Confirmed working correctly. When we inject via 2. Type Change Verification ✅ Confirmed via librarian search in sst/opencode. The // packages/opencode/src/session/message-v2.ts
finish: z.string().optional()Values include: 3. TTY Detection ✅ Fixed in commit if (process.stdout.isTTY !== true) {
return true
}4. Tests Valid suggestion. Will add integration tests in a follow-up PR to keep this one focused. Ready for merge! 🚀 |
…de-yeongyu#216) (code-yeongyu#217) Detects non-interactive environments (CI, opencode run) and prevents session idle when: - Background tasks are still running - Incomplete todos remain in the queue Changes: - Add isNonInteractive() detector for CI/headless environment detection - Export detector from non-interactive-env hook module - Enhance todo-continuation-enforcer to inject prompts BEFORE session.idle - Pass BackgroundManager to todo-continuation-enforcer for task status checks This fix prevents `opencode run` from exiting prematurely when work is pending. 🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
…de-yeongyu#216) (code-yeongyu#217) Detects non-interactive environments (CI, opencode run) and prevents session idle when: - Background tasks are still running - Incomplete todos remain in the queue Changes: - Add isNonInteractive() detector for CI/headless environment detection - Export detector from non-interactive-env hook module - Enhance todo-continuation-enforcer to inject prompts BEFORE session.idle - Pass BackgroundManager to todo-continuation-enforcer for task status checks This fix prevents `opencode run` from exiting prematurely when work is pending. 🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
…de-yeongyu#216) (code-yeongyu#217) Detects non-interactive environments (CI, opencode run) and prevents session idle when: - Background tasks are still running - Incomplete todos remain in the queue Changes: - Add isNonInteractive() detector for CI/headless environment detection - Export detector from non-interactive-env hook module - Enhance todo-continuation-enforcer to inject prompts BEFORE session.idle - Pass BackgroundManager to todo-continuation-enforcer for task status checks This fix prevents `opencode run` from exiting prematurely when work is pending. 🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
Summary
opencode runfrom exiting prematurely when background tasks or incomplete todos remainopencode run, no TTY) and inject continuation prompt BEFOREsession.idlefiresProblem
When running
opencode runin CI (GitHub Actions), the agent would exit immediately after saying "waiting for background tasks..." because:session.idleevent firesopencode runbreaks its event loop onsession.idle→ process exitstodo-continuation-enforceruses 2-second countdown → too slow, process already deadSolution
Hook into
message.updated(fires BEFOREsession.idle) instead ofsession.idle:tool-calls, notunknown)BackgroundManagersession.idlenever fires →opencode runcontinuesChanges
src/hooks/non-interactive-env/detector.tssrc/hooks/non-interactive-env/index.tssrc/hooks/todo-continuation-enforcer.tsmessage.updatedsrc/index.tsBackgroundManagerto enforcerTesting
bun run typecheck- Passbun test- 31 pass, 0 failCloses #216
🤖 GENERATED WITH ASSISTANCE OF OhMyOpenCode