MoonshotAI / MoonshotAI/kimi-code

Hook & permission parity gaps vs Claude Code: split output channels, suppressOutput, ask/defer decisions, updatedInput, missing events, dead ask rules

Open
#2,455 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
7.5k
Forks
1.2k
Avg merge
11h 53m
Merged PRs (30d)
350

Description

Summary

While porting a Claude Code plugin suite to Kimi Code, we mapped the hook and permission surfaces of both binaries (kimi-code 0.30.0 dist/main.mjs vs claude-code 2.1.220). These are the gaps that make faithful ports impossible today. Each item is verified against the shipped code, with a concrete, minimal proposal.

1. Hook output has a single channel rendered to BOTH model and user (the big one)

In agent-core/src/session/hooks/user-prompt.ts (renderUserPromptHookResult), the same messages array builds both:

  • text (wrapped in <hook_result>) → appended to the model context, and
  • message → emitted as the hook.result event → rendered in the TUI transcript.

userPromptHookMessage uses result.message ?? stdout, so any context a hook injects is dumped into the user's terminal. A rules corpus (~18 KB) injected on every UserPromptSubmit renders as a giant block on every prompt.

Claude Code has two separate channels: hookSpecificOutput.additionalContext (model-only) and systemMessage (user-facing one-liner).

Proposal — parse two more fields in the hook JSON output and split the render:

// hook result parsing (already partial: message is read today)
const { message, hookSpecificOutput } = output.data;
const result = {
  message: message ?? hookSpecificOutput?.message,
  context: hookSpecificOutput?.additionalContext,   // NEW: model-only
  structuredOutput: true,
};

// renderUserPromptHookResult: build the two channels from different sources
return {
  event: "UserPromptSubmit",
  message: notices.join("\n"),                       // TUI: short notices only
  text: contexts.map((c) => renderHookResult(event, c)).join("\n"), // model
};

2. No suppressOutput for hook stdout

Claude Code: suppressOutput: boolean — "Hide stdout from transcript". Kimi has no equivalent, so even benign hook chatter is user-visible. Combined with #1, hook authors have zero control over transcript noise.

3. Hooks can only deny — no allow / ask / defer

hookSpecificOutput parsing in kimi only honors permissionDecision: "deny". Claude Code supports allow, ask, and defer (mapped to ask interactively). Consequence: a hook that wants "ask the user" must hard-deny (dead end — retrying the same call denies again) or pass silently. There is no way for a hook to route a decision to the user's approval panel.

Proposal: map permissionDecision: "ask" to the existing requestToolApproval path in AgentPermissionGate.adjudicate — the infrastructure already exists for rule-based asks.

4. No updatedInput on PreToolUse

Claude Code hooks can rewrite tool input before execution (hookSpecificOutput.updatedInput, 79 references in its binary). Kimi hooks can only allow/block. Rewrite hooks (auto-format, path redirects, argument sanitation) are impossible.

5. No prompt-type hooks

Claude Code supports type: "prompt" hooks (an LLM judges the event, e.g. "verify the workflow was followed before Stop"). Kimi is command-only. A heuristic-only replacement is strictly weaker for workflow verification.

6. Missing lifecycle events

Present in claude-code 2.1.220, absent in kimi 0.30.0:

  • TeammateIdle — fires when a background teammate goes idle (used to trigger validation of the teammate's diff). Kimi background subagents produce no equivalent; SubagentStop covers completion but not idle-with-changes.
  • InstructionsLoaded — fires when instruction files load (used for rules-loaded debugging/auditing).

(TaskCompleted has a working equivalent via Notification with matcher task.completed — good.)

7. BUG: user [[permission.rules]] with decision = "ask" never trigger

Repro (0.30.0):

# ~/.kimi-code/config.toml
default_permission_mode = "yolo"

[[permission.rules]]
decision = "ask"
pattern = "Bash(git *)"
  1. Start a new session. 2. Have the agent run git status (matches the pattern — verified against the bundled picomatch semantics).

Expected (docs: rules loaded at session start, first match takes effect): approval panel + PermissionRequest hook event.
Actual: immediate execution. Zero permission events in the session wire, no panel.

Source analysis: the policy chain orders UserConfiguredAskPermissionPolicyService before YoloModeApprovePermissionPolicyService with first-non-void-wins, and AgentPermissionGate.adjudicate() routes kind: "ask" to requestToolApproval unconditionally — so the ask should fire even in yolo. It doesn't, which suggests user-configured rules never reach rulesService.rules (config [[permission.rules]] not loaded/registered at session start), or the gate is not consulted on this path.

Side note for the docs: an arg-pattern with / needs a second rule (Bash(git */**)) because * never crosses /. Worth one line in the permission docs.

Environment

kimi-code 0.30.0 (Homebrew), macOS. All findings verified against dist/main.mjs line-level and live sessions (session wire files under ~/.kimi-code/sessions/).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading agent-core/src/session/hooks/user-prompt.ts, especially renderUserPromptHookResult, then trace AgentPermissionGate.adjudicate and the user-configured permission policy. Reproduce the ask-rule case from the issue using the stated config and session wire files. Done requires agreed, tested behavior for the proposed hook channels, permission decisions, input updates, lifecycle events, and rule loading.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
cli, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.