Port four Claude Code conveniences: background bash, lifecycle hooks, worktree isolation, statusLine/defer_tools config#4
Merged
Conversation
Adds foundations for four Claude-Code-style features identified in
~/.claude/plans/check-out-what-features-shimmering-moler.md. Each item
type-checks cleanly (`tsgo --noEmit` on both packages/plugin and
packages/opencode apart from the pre-existing TuiThemeCurrent sidebar
error) and existing bash + registry test suites still pass.
1. Lifecycle & prompt hooks (packages/plugin/src/index.ts)
Adds five Hooks entries: session.start, session.end,
chat.prompt.submit, chat.stop, notify. session.start/end are bridged
from existing session.created/session.deleted bus events in
packages/opencode/src/plugin/index.ts so plugin authors can subscribe
by name. chat.prompt.submit and chat.stop are triggered from
packages/opencode/src/session/prompt.ts at the entry and exit of the
prompt pipeline; plugins can inject context parts or block the
prompt via the submit hook. notify is types-only — the TUI/desktop
consumer is follow-up.
2. Background bash (+ bash_output / kill_shell)
New packages/opencode/src/shell/background.ts service manages a
registry of detached child processes with rolling 1 MB per-shell
buffers. The bash tool grows a run_in_background parameter that
routes to the service and returns a shell_id. Two new tools read
incremental output (bash_output) and terminate (kill_shell). The
three test layers that build ToolRegistry were updated to provide
BackgroundShell.defaultLayer.
3. Task worktree isolation
packages/opencode/src/tool/task.ts grows an isolation: "worktree"
option. When set on a fresh (non-resumed) task, the tool runs
`git worktree add -b <branch> <tmp> HEAD`, prepends the worktree
path/branch to the subagent's prompt so the child uses workdir: on
bash/edit tools, and after completion auto-removes the worktree if
clean or reports path+branch back so the parent can act on it.
Note: full filesystem-level isolation (rerouting Instance.directory
per-session) is out of scope for this change; the current approach
relies on the subagent respecting the prompt's workdir guidance.
4. statusLine + experimental.defer_tools config
Adds config.statusLine ({ type: "command", command, padding? }) and
config.experimental.defer_tools to the config schema. The schemas
and documentation are in place; TUI rendering for statusLine and
the ToolSearch registry indirection for defer_tools are intentionally
scoped as follow-up — both require large, independently-testable
changes (TUI status bar, tool registry refactor) that were better
landed separately.
Follow-up tickets this change implies:
- Wire notify hook to TUI/desktop notification surface
- Consume config.statusLine in the TUI status bar with throttled refresh
- Build the ToolSearch indirection that respects experimental.defer_tools
- Consider rerouting Instance.directory per-session so Task worktree
isolation enforces the boundary in the filesystem layer rather than
relying on subagent cooperation
https://claude.ai/code/session_01XTJvnZWsDnZigTBLi2U1Ud
…ll_id Code review of the initial port found several quality issues; fixing them before opening the PR. - Drop dead re-export of Shell from background.ts — only killTree is used. - Drop unused list() method and ListResult type — no caller. - Drop unused cleanup() from the public Interface; the instance-scope teardown now happens via Effect.addFinalizer inside InstanceState.make, which kills any live child processes (via killTree) when the scope closes. This was the behaviour the original commit's docstring promised but didn't actually wire up. - Drop unused cwd field from Handle. - Convert ShellNotFound from a plain Error (which wouldn't match Effect.catchTag by _tag) to Schema.TaggedErrorClass so the tools can catch it structurally. - bash_output / kill_shell: instead of Effect.orDie on a missing shell_id, catch ShellNotFound and return a friendly tool result so the model can recover instead of producing a hard defect. Typecheck clean on both @opencode-ai/plugin and opencode (only the pre-existing TuiThemeCurrent sidebar error remains). bash.test.ts (21) and registry.test.ts (3) still pass. https://claude.ai/code/session_01XTJvnZWsDnZigTBLi2U1Ud
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ports four Claude-Code-style conveniences identified during a feature-gap audit against OpenCode. Each item is type-clean on both
@opencode-ai/pluginandopencode(only the pre-existingTuiThemeCurrentsidebar error remains unrelated).bash.test.ts(21) andregistry.test.ts(3) still pass.1. Lifecycle & prompt hooks (
packages/plugin/src/index.ts)Five new entries on
Hooks:session.start/session.end— fired frompackages/opencode/src/plugin/index.tsby bridging existingsession.created/session.deletedbus events, so plugin authors can subscribe by name.chat.prompt.submit— fires when the user submits a prompt but before the LLM call. Plugins can append parts to inject context (e.g. repo state) or setoutput.blockto a reason string to short-circuit the turn.chat.stop— fires when the assistant turn finishes (success or error), withabortedindicating which path.notify— user-visible notification surface; types only in this PR, TUI/desktop wiring is follow-up.Triggers for
chat.prompt.submitandchat.stopare wired inpackages/opencode/src/session/prompt.tsaround theloop(...)call.2. Background bash +
bash_output/kill_shellNew
BackgroundShellservice atpackages/opencode/src/shell/background.ts— an instance-scoped registry of detached child processes with rolling 1 MB per-shell output buffers. Shutdown is handled through anEffect.addFinalizerinsideInstanceState.make, which callskillTreeon any live shells when the scope closes.bashtool grows arun_in_background?: booleanparameter. When true it spawns viaBackgroundShell.startand returns ashell_idimmediately instead of blocking.bash_outputtool drains the rolling buffer for a givenshell_idand reports status + exit code.kill_shelltool sends SIGTERM then SIGKILL (via the existingkillTreehelper) and returns the final buffered output.shell_ids produce a recoverable tool result (viaSchema.TaggedErrorClass+Effect.catchTag), not a hard defect.ToolRegistry.layer, plus all three test layers that build it (bash.test.ts,prompt-effect.test.ts,snapshot-tool-race.test.ts), are updated to provideBackgroundShell.defaultLayer.3. Task worktree isolation (
packages/opencode/src/tool/task.ts)Adds
isolation: "worktree"to the Task parameters. When set on a fresh (non-resumed) task, the tool runsgit worktree add -b <branch> <tmp> HEAD, prepends the worktree path/branch to the subagent's prompt so the child usesworkdir:on bash/edit tools, and after completion auto-removes the worktree if clean — or reports path+branch back so the parent can act on it.Caveat: filesystem-level isolation (rerouting
Instance.directoryper session) is intentionally out of scope. The current approach relies on the subagent honoring the prompt'sworkdirguidance.4.
config.statusLine+experimental.defer_toolsAdds two new config schema entries at
packages/opencode/src/config/config.ts:statusLine: { type: "command", command, padding? }— mirrors Claude Code's statusLine contract.experimental.defer_tools: boolean— opt-in flag for a futureToolSearchindirection that would defer non-core tool schemas to reduce prompt tokens.Schema + documentation only; TUI rendering for
statusLineand the registry refactor to honordefer_toolsare intentionally scoped as follow-ups — both are large, independently-testable changes best landed separately.Follow-ups this change implies
notifyhook to the TUI/desktop notification surface.config.statusLinein the TUI status bar with throttled refresh.ToolSearchindirection that respectsexperimental.defer_tools.Instance.directoryper-session so Task worktree isolation is enforced in the filesystem layer rather than relying on subagent cooperation.Test plan
bun run typecheckclean onpackages/pluginandpackages/opencode(apart from the pre-existingTuiThemeCurrenterror inpackages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx— reproduces atorigin/dev, not introduced here).bun test test/tool/bash.test.ts→ 21 pass.bun test test/tool/registry.test.ts→ 3 pass.run_in_background: true, read incremental output viabash_output, kill withkill_shell.isolation: "worktree"in a git repo, confirm the worktree is created under$TMPDIR/opencode-task-*, the subagent writes there, and a clean run auto-removes it.session.start,session.end,chat.prompt.submit,chat.stop) and confirm ordering over a full session.https://claude.ai/code/session_01XTJvnZWsDnZigTBLi2U1Ud