Skip to content

fix: prevent premature exit in non-interactive mode when tasks pending#217

Merged
code-yeongyu merged 1 commit into
devfrom
fix/non-interactive-premature-exit-216
Dec 25, 2025
Merged

fix: prevent premature exit in non-interactive mode when tasks pending#217
code-yeongyu merged 1 commit into
devfrom
fix/non-interactive-premature-exit-216

Conversation

@code-yeongyu

Copy link
Copy Markdown
Owner

Summary

  • Prevent opencode run from exiting prematurely when background tasks or incomplete todos remain
  • Detect non-interactive mode (CI, opencode run, no TTY) and inject continuation prompt BEFORE session.idle fires

Problem

When running opencode run in CI (GitHub Actions), the agent would exit immediately after saying "waiting for background tasks..." because:

  1. Agent finishes response → session.idle event fires
  2. opencode run breaks its event loop on session.idle → process exits
  3. Existing todo-continuation-enforcer uses 2-second countdown → too slow, process already dead

Solution

Hook into message.updated (fires BEFORE session.idle) instead of session.idle:

  1. Detect terminal finish reason (not tool-calls, not unknown)
  2. Check for running background tasks via BackgroundManager
  3. Check for incomplete todos
  4. If either exists in non-interactive mode → inject prompt immediately
  5. This keeps the session active → session.idle never fires → opencode run continues

Changes

File Change
src/hooks/non-interactive-env/detector.ts NEW: Detect CI/non-interactive environment
src/hooks/non-interactive-env/index.ts Export detector
src/hooks/todo-continuation-enforcer.ts Add preemptive injection on message.updated
src/index.ts Pass BackgroundManager to enforcer

Testing

  • bun run typecheck - Pass
  • bun test - 31 pass, 0 fail

Closes #216


🤖 GENERATED WITH ASSISTANCE OF OhMyOpenCode

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)
@github-actions

github-actions Bot commented Dec 25, 2025

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA. Thank you! ✅
Posted by the CLA Assistant Lite bot.

@code-yeongyu

Copy link
Copy Markdown
Owner Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Dec 25, 2025
@code-yeongyu code-yeongyu merged commit 8d9b68d into dev Dec 25, 2025
2 of 3 checks passed
@code-yeongyu code-yeongyu deleted the fix/non-interactive-premature-exit-216 branch December 25, 2025 06:27
@code-yeongyu

Copy link
Copy Markdown
Owner Author

@sisyphus-dev-ai review this

@sisyphus-dev-ai

Copy link
Copy Markdown
Collaborator

👋 Hey @code-yeongyu! I'm on it...

@sisyphus-dev-ai sisyphus-dev-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

  1. Root Cause Analysis: Correctly identified that the 2-second countdown is too slow for opencode run which exits immediately on session.idle

  2. Smart Timing: Hooking into message.updated BEFORE session.idle fires is the right approach - prevents the session from going idle in the first place

  3. Robust Detection: detector.ts covers multiple cases:

    • CI environment variables (CI, GITHUB_ACTIONS)
    • OpenCode-specific variables (OPENCODE_RUN, OPENCODE_NON_INTERACTIVE)
    • TTY detection as fallback
  4. Proper State Management:

    • preemptivelyInjectedSessions prevents duplicate injections
    • Cleared on user message and injection failure
    • Respects recovery/error states
  5. BackgroundManager Integration: Checks for running background tasks, not just todos - comprehensive solution

  6. 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:

  1. Runs opencode run with background tasks
  2. Verifies the process doesn't exit prematurely
  3. 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!

@code-yeongyu

Copy link
Copy Markdown
Owner Author

Review Response

Thanks @sisyphus-dev-ai for the thorough review!

Answers to Questions

1. Injected Message Role

✅ Confirmed working correctly. When we inject via ctx.client.session.prompt(), OpenCode creates a user message. On the next message.updated event with role === "user", we clear preemptivelyInjectedSessions (line 294-295). This allows re-injection on subsequent finishes.

2. Type Change Verification

✅ Confirmed via librarian search in sst/opencode. The finish field is defined as:

// packages/opencode/src/session/message-v2.ts
finish: z.string().optional()

Values include: "tool-calls", "stop", "length", "error", etc. The boolean type in preemptive-compaction/index.ts is incorrect and should be fixed separately.

3. TTY Detection

✅ Fixed in commit 500a8bc. Changed to explicit check:

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! 🚀

codewithkenzo pushed a commit to codewithkenzo/oh-my-opencode that referenced this pull request Jan 9, 2026
codewithkenzo pushed a commit to codewithkenzo/oh-my-opencode that referenced this pull request Jan 9, 2026
…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)
sssgun pushed a commit to sssgun/oh-my-opencode that referenced this pull request Jan 18, 2026
sssgun pushed a commit to sssgun/oh-my-opencode that referenced this pull request Jan 18, 2026
…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)
Rolloniel pushed a commit to Rolloniel/oh-my-opencode that referenced this pull request Feb 22, 2026
Rolloniel pushed a commit to Rolloniel/oh-my-opencode that referenced this pull request Feb 22, 2026
…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)
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.

when sisyphus agent is working, and background tasks or remaining todos left, it must not be 'stopped'

2 participants