Skip to content

Port four Claude Code conveniences: background bash, lifecycle hooks, worktree isolation, statusLine/defer_tools config#4

Merged
TTK95 merged 2 commits into
devfrom
claude/explore-claude-opencode-features-i6qlX
Apr 23, 2026
Merged

Port four Claude Code conveniences: background bash, lifecycle hooks, worktree isolation, statusLine/defer_tools config#4
TTK95 merged 2 commits into
devfrom
claude/explore-claude-opencode-features-i6qlX

Conversation

@TTK95
Copy link
Copy Markdown
Owner

@TTK95 TTK95 commented Apr 22, 2026

Summary

Ports four Claude-Code-style conveniences identified during a feature-gap audit against OpenCode. Each item is type-clean on both @opencode-ai/plugin and opencode (only the pre-existing TuiThemeCurrent sidebar error remains unrelated). bash.test.ts (21) and registry.test.ts (3) still pass.

1. Lifecycle & prompt hooks (packages/plugin/src/index.ts)

Five new entries on Hooks:

  • session.start / session.end — fired from packages/opencode/src/plugin/index.ts by bridging existing session.created / session.deleted bus 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 set output.block to a reason string to short-circuit the turn.
  • chat.stop — fires when the assistant turn finishes (success or error), with aborted indicating which path.
  • notify — user-visible notification surface; types only in this PR, TUI/desktop wiring is follow-up.

Triggers for chat.prompt.submit and chat.stop are wired in packages/opencode/src/session/prompt.ts around the loop(...) call.

2. Background bash + bash_output / kill_shell

New BackgroundShell service at packages/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 an Effect.addFinalizer inside InstanceState.make, which calls killTree on any live shells when the scope closes.

  • bash tool grows a run_in_background?: boolean parameter. When true it spawns via BackgroundShell.start and returns a shell_id immediately instead of blocking.
  • New bash_output tool drains the rolling buffer for a given shell_id and reports status + exit code.
  • New kill_shell tool sends SIGTERM then SIGKILL (via the existing killTree helper) and returns the final buffered output.
  • Missing shell_ids produce a recoverable tool result (via Schema.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 provide BackgroundShell.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 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.

Caveat: filesystem-level isolation (rerouting Instance.directory per session) is intentionally out of scope. The current approach relies on the subagent honoring the prompt's workdir guidance.

4. config.statusLine + experimental.defer_tools

Adds 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 future ToolSearch indirection that would defer non-core tool schemas to reduce prompt tokens.

Schema + documentation only; TUI rendering for statusLine and the registry refactor to honor defer_tools are intentionally scoped as follow-ups — both are large, independently-testable changes best landed separately.

Follow-ups this change implies

  • Wire the notify hook to the 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 is enforced in the filesystem layer rather than relying on subagent cooperation.

Test plan

  • bun run typecheck clean on packages/plugin and packages/opencode (apart from the pre-existing TuiThemeCurrent error in packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx — reproduces at origin/dev, not introduced here).
  • bun test test/tool/bash.test.ts → 21 pass.
  • bun test test/tool/registry.test.ts → 3 pass.
  • Manual smoke: start a dev server with run_in_background: true, read incremental output via bash_output, kill with kill_shell.
  • Manual smoke: spawn a Task with 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.
  • Write a throwaway plugin that logs each new hook firing (session.start, session.end, chat.prompt.submit, chat.stop) and confirm ordering over a full session.

https://claude.ai/code/session_01XTJvnZWsDnZigTBLi2U1Ud

claude added 2 commits April 22, 2026 06:33
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
@TTK95 TTK95 merged commit 3e2f619 into dev Apr 23, 2026
@TTK95 TTK95 deleted the claude/explore-claude-opencode-features-i6qlX branch April 23, 2026 13:32
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.

2 participants