Skip to content

D300 — Kiro gets a session-start hook through a standalone file Cartographer owns, limited to the V3 TUI

Decision. KB hooks and the bootstrap hook (D60) reach Kiro as entries of ~/.kiro/hooks/cartographer.json. This is a standalone "version": "v1" hook file that Cartographer owns whole. It is regenerated from the set of registered hooks and removed with its last entry. The hooks' files sit in ~/.kiro/hooks/cartographer/<name>/. Kiro fires these hooks only in kiro-cli chat --v3 --tui, so the hook mechanism declares that limit (SessionHookLimit). status qualifies the hook count with it, and connect, status and doctor keep advising the scheduled timer (D140) for Kiro, naming the limit instead of calling Kiro hookless (D189). SessionStart, UserPromptSubmit and Stop are declared to reach Kiro. Its tool events are not, because nothing exercised them.

This supersedes D140's Kiro conclusion, "Kiro has no registrable hook". In the V3 TUI it does. On 2.26.1 the built-in kiro_default can also be shadowed in v2, which D140 had found impossible on 2.20.0. Shadowing stays refused.

Why. The question D140 asked was never whether Kiro has hooks, but whether a hook Cartographer writes fires in normal use. Re-probes on Kiro CLI 2.26.1 and 2.27.0 split the answer by mode. Standalone hook files fire under chat --v3 --tui and nowhere else: not in the default interactive UI (a new TUI over the v2 engine since 2.27.0), and not in any --no-interactive run. An agent's own hooks fire in every mode, but only for that agent. The standalone file is the only mechanism that needs nothing of the user's: no agent config, no setting, no file it does not own. The cost is coverage. Most Kiro sessions still sync only on the timer, and saying "installed" without the limit would be the false positive D189 exists to stop.

The loader reads only the *.json files directly inside ~/.kiro/hooks/. This is KAS loadHooksDir, and a probe confirmed that a file in a subdirectory was not loaded. So the scripts can live one level below the registration without being parsed as hook files themselves. The schema requires at least one entry, so the file must go rather than stay empty.

Alternatives rejected. - Shadow kiro_default with a copy that adds a hook. This replaces the built-in's prompt and tools, which is the intrusion D140 refused. - An opt-in Cartographer-owned default agent (chat.defaultAgent) with an agentSpawn hook. This was the plan's WP2. It was dropped on its own verify-first step (#493). An agent with no tools key gets no tools (0 against kiro_default's 14), and the default's prompt is compiled into the binary. Matching the default means declaring the tool set and copying a vendor prompt that changes between versions. - Write a hook into the user's own agent configs. An edit to a file the user curates, firing only for the agents Cartographer happened to find. - Mirror the registration into the workspace .kiro/hooks/. It fires in the same single mode as the global file, so it reaches no session the global one misses. The workspace cell stays unsupported. - Record cartographer.json as a managed file of each hook. Prune removes a managed file by path, so pruning one hook would delete every other hook's registration. The hooks' own files are the managed record, and the shared file is edited by entry name, like Antigravity's hooks.json. - Rewrite hook commands to forward slashes on Windows (D267). Kiro runs command hooks through PowerShell or cmd.exe on Windows, not a POSIX shell.

Consequences. A non-recursive loader is now a dependency of the matrix cell. If Kiro ever reads subdirectories of ~/.kiro/hooks/, every materialized hook.json becomes a malformed hook file, and the cell has to move out (the comment at the cell and harnesses.md say so). When the default kiro-cli chat starts firing standalone hooks, drop the limit on the Kiro hook mechanism, and the timer advice goes with it. Kiro IDE and Kiro Crew stay out of the matrix until they are probed on a machine that has them. Cross-references: D140, D195, D189.