Skip to content

kiro universal agent-config format: task-init's hook merge and task-remove's strip break on an upgraded .kiro/agents/*.json (#267) - #270

Merged
martin-conur merged 2 commits into
mainfrom
task/kiro-universal-config
Oct 20, 2026
Merged

martin-conur merged 2 commits into
mainfrom
task/kiro-universal-config

Conversation

@martin-conur

@martin-conur martin-conur commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Closes #267

What changed

  • Merge (kiro-radio-hooks-jq-merge + kiro-radio-stray-hook-verify, byte-identical across kiro-gh / kiro-local / kiro-notion, tools/check-drift.sh clean): radio entries are recognised by command in either shape (.command or .action.command). Missing ones are added in the shape the file already uses. In the universal shape that is {name: "radio-<trigger>", trigger, action: {type: "command", command}, timeout: 10}. Configs are never converted between formats. The stale-radio-command warning reads the array too.
  • Strip (_la_strip_agent_hooks in lib/loadout-artifacts.sh): drops radio entries from an array hooks, keeps the user's own entries verbatim, and drops hooks only once it is empty.
  • Warning / docs: the true caveat is gone from aw_kiro_warn_upgrade_gate, the three kiro steering templates and the README. The warning still names false as the fix.
  • An upgraded config never matches the copy task-init ships, because kiro-cli also rewrites tools and adds permissions / kiroUpgrade. So removal keeps it with only the radio hooks stripped (the existing keep-bias), and --purge still deletes it.

How the fixtures were produced

  • Source: tests/fixtures/kiro-universal/*.json are the unedited output of kiro-cli agent upgrade --no-backup <dir> on kiro-cli 2.32.0, run over the minimal inputs kept in v2/. Those inputs are in task-init's own shape, with the radio commands copied verbatim from what kiro-gh's installer writes. tests/fixtures/kiro-universal/README.md has the regeneration recipe.
  • What I ran: kiro-cli agent upgrade and kiro-cli agent validate, with an explicit scratch directory as the target, under a scratch TASK_FORCE_HOME and with TASK_FORCE_ROLE unset. I did not run any kiro-cli settings write.
  • Deviation from the PM's guardrail: that guardrail reached me after the fixtures were already generated, and HOME was not pointed at a scratch dir for those runs. Afterwards I checked that nothing else was touched. No file under ~/.kiro has a modification time later than 07:22 today, which is before this session started. The global agent configs and symlinks are unchanged. Neither this repo nor its worktree has a .kiro/.
  • Also verified on 2.32.0:
    • A config the merge has edited passes kiro-cli agent validate.
    • kiro-cli agent upgrade --dry-run still reports that config already universal, so the kiroUpgrade fingerprint is not tripped.
    • validate rejects a malformed array entry, so the shape check is real.

Tests

  • ./run_tests.sh: 1383/1383 pass (round 1).
  • tools/check-drift.sh: clean.
  • shellcheck -x on the changed shell files: clean.
  • New tests:
    • Installer (kiro-gh): a re-run over an upgraded config is a no-op; entries are added in the array shape (name, trigger, type, command, timeout); the merge is idempotent; object-shape configs beside an upgraded one stay objects; the stray warning reads the array.
    • Installer (kiro-local / kiro-notion): one test each that the entry carries that loadout's own --loadout default.
    • Strip: through task-remove (apply and --dry-run, plus a radio-only config) and through task-config set.
    • Warning: it no longer carries the caveat.
  • Mutation checks:
    • Against the pre-fix kiro-gh/bin/task-init, 4 of the 5 kiro-gh installer tests went red. The fifth, "object configs stay objects", guards the opposite mistake. A merge mutated to normalise every config to the universal shape turned it red.
    • Against the pre-fix lib, all 4 strip tests went red.

Review round 1 (changes-requested): both nits

Nit 1: a hook-less universal config. It used to get an object hooks, a hybrid. It now gets an array.

  • Marker: has("permissions"), not has("kiroUpgrade"). I probed kiro-cli 2.32.0 with agent upgrade --dry-run, which classifies each file:
    • An object hooks in any file that has permissions is reported out of sync (not rebuilt), whether or not kiroUpgrade is present. An array is accepted in every case.
    • Drop permissions from an upgraded file and it becomes upgradeable again. Drop kiroUpgrade instead and it stays already universal.
    • A V3-only file (no allowedTools) carries no fingerprint. So kiroUpgrade would give exactly that file the hybrid.
    • No shipped agent config has permissions, so fresh installs keep object hooks.
  • New fixture: no-hooks.json is real kiro-cli agent upgrade output of a hook-less v2 config. It has no hooks key, and does have permissions and kiroUpgrade.
  • Two tests:
    • The real fixture gets an array hooks with the three radio-<trigger> entries, and nothing else in the file changes.
    • The fixture with kiroUpgrade dropped still gets an array. This pins permissions as the marker.
  • Mutation check: both tests fail on the previous head c0d13d8. The second one also fails with the marker swapped to has("kiroUpgrade").

Nit 2: duplicate hook names. kiro-cli 2.32.0 does not reject them. agent validate, agent upgrade --dry-run and agent list all accept a config with two hooks named radio-stop. A one-line comment in the merge region now says so. Not tested: whether both of the hooks fire in a live session. Note that agent validate cannot settle any of this. It accepted every variant I tried, the hybrid included, and it exits 0 even when it prints an error.

How kiro-cli was run this round: every run had HOME pointed at a scratch dir, a scratch TASK_FORCE_HOME and TASK_FORCE_ROLE unset. Under a scratch HOME the kiro-cli launcher fails, because it looks for $HOME/.local/bin/kiro-cli-chat. So I called /Applications/Kiro CLI.app/Contents/MacOS/kiro-cli-chat (2.32.0) directly. kiro-cli wrote its state (.kiro/settings/cli.json, data.sqlite3) into the scratch HOME. The only files in the real ~/.kiro modified today are chat sessions from another repo (recommender-systems). I also regenerated the three round-0 fixtures under the scratch HOME, and they came out byte-identical to the committed ones.

The three installers are still drift-identical.

Upgrading: removal needs no re-run, since lib/ takes effect through the symlinks. Re-run task-init <kiro loadout> to get the merge fix and the updated steering doc.

🤖 Generated with Claude Code

…emove's strip break on an upgraded .kiro/agents/*.json: handle the array shape (#267)

kiro-cli's V3 engine can rewrite `.kiro/agents/*.json` into its
"universal" format, where `hooks` is an array of
`{name, trigger, action: {type, command}, timeout}`. task-init's merge
failed on every agent with `Cannot index array with string "agentSpawn"`,
and the strip in `lib/loadout-artifacts.sh` silently skipped the array,
leaving every radio hook behind on task-remove / task-config set.

- `kiro-radio-hooks-jq-merge` / `kiro-radio-stray-hook-verify` (all three
  kiro installers, drift-guarded): recognise radio entries by command in
  either shape, add missing ones in the shape the file already uses
  (`radio-<trigger>`, timeout 10), never convert between formats.
- `_la_strip_agent_hooks`: drops radio entries from the array too, and
  drops `hooks` once empty.
- `aw_kiro_warn_upgrade_gate` and the kiro steering docs / README lose the
  "true cannot be merged into yet" caveat.

Fixtures in `tests/fixtures/kiro-universal/` are unedited
`kiro-cli agent upgrade` output (kiro-cli 2.32.0), inputs in `v2/`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@martin-conur

Copy link
Copy Markdown
Owner Author

Spec compliance (#267)

All three asks are delivered:

  • Merge reads the array shape. Radio entries are recognised by command (.command // .action.command?) in kiro-radio-hooks-jq-merge and kiro-radio-stray-hook-verify. The regions are byte-identical across the three kiro installers, and CI's drift check is green.
  • Insert in the shape the file already uses, never convert. add_radio_u appends {name, trigger, action:{type:"command",command}, timeout:10} only to an array hooks. The object branch is unchanged.
  • Strip handles the array. _la_strip_agent_hooks drops radio entries, keeps the user's own entries verbatim, and deletes hooks only once it is empty.

The #257 warning no longer carries the true caveat, and the steering templates, README and CHANGELOG are updated. The CHANGELOG entry includes the upgrading note. The fixtures are real kiro-cli agent upgrade output, with a regeneration recipe.

Code-review findings

No blockers. CI ran and passed for head c0d13d8 (bats on ubuntu and macOS, ShellCheck, drift check). I did not get a local ./run_tests.sh result: bats isn't on this tab's PATH and the full-suite run didn't finish in time. So test results rest on CI and the PR's own report.

Nits, to fix in this PR if cheap:

  1. A universal config with no hooks key gets an object-shaped hooks. The branch test is (.hooks | type) == "array", so a hook-less upgraded config (kiroUpgrade present, no hooks) falls into the object branch and gets hooks: {…} added. That is a hybrid file: universal everywhere else, legacy object hooks. The PR verified agent validate on merged array configs only. Either detect the universal format by has("kiroUpgrade") and create hooks as [] first, or add a fixture and test showing kiro-cli accepts the hybrid. The "never convert" guarantee doesn't currently cover this case.
  2. Entry name collisions are not handled. The new entry is named radio-<trigger>, while kiro-cli's own upgrade names entries <trigger>-<n> (e.g. agentSpawn-0). That is fine in practice, but nothing guards against a user entry already named radio-stop. It is only worth a one-line comment if kiro-cli requires unique names; I did not check.

Verdict

clean-with-nits. Nit 1 is the only one with real behaviour behind it. PM can merge as is, or forward nit 1 in a single round.

…emove's strip break on an upgraded .kiro/agents/*.json: hook-less universal configs get an array, name collisions documented (#267)

Review round 1 on PR #270.

- A universal config with no `hooks` key fell into the object branch and
  became a hybrid, which kiro-cli's upgrade reports as "out of sync (not
  rebuilt)". The merge now takes the shape from `permissions` when `hooks`
  is absent. That is the field kiro-cli keys the hooks shape on (probed on
  2.32.0). `kiroUpgrade` cannot be the marker: a V3-only config has no
  fingerprint.
- Duplicate hook names: kiro-cli 2.32.0 accepts them (`agent validate`,
  `agent upgrade`, `agent list`). Recorded in a comment; nothing avoids them.
- New real fixture `no-hooks.json`, generated under a scratch HOME, plus two
  tests. Both fail on the previous head, and the second also fails with the
  marker swapped to `kiroUpgrade`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@martin-conur
martin-conur merged commit 1bc623e into main Oct 20, 2026
4 checks passed
@martin-conur
martin-conur deleted the task/kiro-universal-config branch October 20, 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.

kiro universal agent-config format: task-init's hook merge and task-remove's strip break on an upgraded .kiro/agents/*.json

1 participant