Skip to content

feat(mod): run a built-in slash command the user asks the session for - #60

Merged
omartelo merged 1 commit into
mainfrom
feat/self-command
Oct 9, 2026
Merged

omartelo merged 1 commit into
mainfrom
feat/self-command

Conversation

@omartelo

@omartelo omartelo commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

Lets a Claude Code session inside lich run one of its own built-in slash commands when the user asks for it ("run /compact"). This is plugin-only. Nothing goes through lich, whose control refuses a session acting on itself on purpose (lich docs/ceilings.md, "A session cannot control itself").

Design

hooks/self-command.js (registered by hooks/lich.js) works through the Skill tool, the tool the model already reaches for:

  • tool.describe on Skill appends one paragraph saying a built-in slash command can be named there when the user asks, and that it runs once the turn ends.
  • tool.call on Skill runs the native tool first, so skills and the built-ins it serves as prompts (/init, /review) stay its own. The mod steps in only when that call errored and $.command.list() names the command as builtin. It then answers in the Skill tool's inline shape ({ success, commandName }), with a context note saying the command is queued and the turn should end. It calls $.command.run from $.clock.after(0), because $.command.run rejects inside a hook the turn is waiting on. The command runs once the session is idle, after the turn that asked for it.
  • /model and /effort get a deny that points at lich's per-session override, and never run. Through a mod, Claude Code saves them as the default for every new session.
  • If a queued command then fails, its error goes to a toast.
  • The mod is active only with LICH_SESSION_ID set in an interactive session.

Measured (Claude Code 2.1.295)

  • A tool of the mod's own is not possible. Claude Code lists it as mcp__lich__<name>, and $.tool.register refuses with the session already has an MCP server named "lich" in its MCP config; registering would replace it. That covers every lich session.
  • Before this change, asked "roda /compact pra mim", the model answered that it could not, since /compact is a CLI command and not a skill. Skill(compact) returns isError with compact is a built-in CLI command, not a skill….
  • With this branch (--plugin-dir, lich's MCP server in the config, the same prompt): the model called Skill(compact) and said it would run once the turn ended. The turn ended, and /compact then ran and compacted the conversation. Claude Code accepted the inline result.
  • /model, /effort and /cost were not run live.

Known ceilings (in docs/self-command.md)

  • The transcript shows the call as "Successfully loaded skill".
  • A command that opens a dialog (/cost) holds the command queue until Esc is pressed. $.command.list() cannot tell such commands apart, so none is refused.

Changes

  • hooks/self-command.js, registered in hooks/lich.js.
  • Docs: docs/self-command.md, plus CLAUDE.md, README.md, docs/providers.md, and CHANGELOG under [Unreleased] › Added.
  • Tests: tests/self-command.test.mjs (9) and tests/self-command.engine.test.ts (2). The kit has no command.list, so a test hook stands in for the session's list.

No version bumped.

Tests

  • node --test tests/*.test.mjs: 372 pass, 0 fail.
  • claude plugin test .: 31 pass, 0 fail.
  • claude plugin validate .claude-plugin/plugin.json: passes. The only warnings are ones that already existed, plus a "gating hook without .catch" warning on the new tool.call hook, the same one every other mod already gets.

Told to run /compact, the model answered that it could not: the Skill tool
refuses a built-in. hooks/self-command.js takes that refusal and queues the
command with $.command.run from a timer, so it runs on the session itself once
the asking turn ends; the Skill tool's description says so. /model and /effort
are refused, since run this way they become every session's default.

A tool of the mod's own is not possible: $.tool.register refuses
mcp__lich__* in a session whose MCP config has lich's server.
@omartelo
omartelo merged commit c0f6dd2 into main Oct 9, 2026
3 checks passed
@omartelo omartelo mentioned this pull request Oct 9, 2026
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.

1 participant