Skip to content

[Bug]: 2.2.17's Kiro IDE adapter reads USER_PROMPT, empty on IDE 1.0.89 — audit/sensor (#417) and log-subagent (#459) regress #543

Description

@wowzoo

Apologies for the churn first. My earlier reports (#417/#459, filed against 0.12.333) framed the payload channel as USER_PROMPT, and PR #471 (2.2.17) built the fix against exactly that — the Kiro IDE adapter reads process.env.USER_PROMPT. On the current IDE build (1.0.89) the channel is the opposite: the payload arrives on stdin, and USER_PROMPT is empty. So the shipped adapter reads "", and the audit/sensor and log-subagent hooks go silent again. The adapter is byte-identical from 2.2.17 through 2.2.19, so this is the state of the latest release.

I re-verified live on 2.2.19 / IDE 1.0.89 (probe hook + a full workflow run, AIDLC_HOOK_DEBUG on).

Shared root cause

  • Channel = stdin JSON; USER_PROMPT is empty. The stdin JSON channel is the IDE 1.x hook contract (1.0.52 onward per the Kiro docs); I tested on 1.0.89. The adapter's own hookDebug logs hasUserPrompt=false on every PostToolUse dispatch, so the shipped process.env.USER_PROMPT read (aidlc-kiro-adapter.ts:73) leaves ide = {} and every branch degrades to a no-op.
  • Field names are snake_case tool_name / tool_input / tool_response.
  • tool_input is {} on every event; the write path / agent identity live only in tool_response prose (Created the <PATH> file. / Replaced text in <PATH>), which extractWrittenPath (:161-169) already matches verbatim. So only the acquisition step reads the wrong channel.

Fix 1 — acquisition (fixes #417 audit/sensor, and unblocks #459)

Read stdin first, fall back to USER_PROMPT, map onto the {tool_name, tool_input, tool_response} shape the downstream already parses:

// aidlc-kiro-adapter.ts — replace the USER_PROMPT-only acquisition block
let ide: IdeHookContext = {};
{
  // 1) stdin first (IDE 1.x). The IDE may open stdin but not close it,
  //    so race the read against a short timeout to avoid hanging.
  let stdinText = "";
  if (!process.stdin.isTTY) {
    try {
      stdinText = await Promise.race([
        Bun.stdin.text(),
        new Promise<string>((r) => setTimeout(() => r(""), 2000)),
      ]);
    } catch { stdinText = ""; }
  }
  if (stdinText.trim().length > 0) {
    try {
      const s = JSON.parse(stdinText);              // { tool_name, tool_input, tool_response }
      ide = {
        toolName: s.tool_name,
        toolArgs: s.tool_input,
        toolResult: typeof s.tool_response === "string" ? s.tool_response : "",
        toolSuccess: undefined,   // stdin carries no success flag
      };
    } catch { ide = {}; }
  } else {
    // 2) USER_PROMPT env fallback (IDE 0.12.x).
    const raw = process.env.USER_PROMPT ?? "";
    if (raw.length > 0) { try { ide = JSON.parse(raw); } catch { ide = {}; } }
  }
}

(Requires the enclosing dispatch to be async / top-level await; the timeout race keeps it from hanging when the IDE holds stdin open.) Reading stdin first with the USER_PROMPT fallback covers 1.0.52+ without dropping the 0.12.x contract. With this in place on 2.2.19, a live 1.0.89 run recorded ARTIFACT_CREATED / ARTIFACT_UPDATED / SENSOR_FIRED / SENSOR_PASSED — the first time those land in the audit on Kiro IDE for me. This is the #417 fix.

Fix 2 — subagent tool name (#459 log-subagent), on top of Fix 1

With the payload arriving, SUBAGENT_COMPLETED can be emitted — but PR #471 filters on invoke_sub_agent, and that is not the name the IDE sends for aidlc delegates on 1.0.89. In the same clean run every delegate completion arrived under subagent_<agentname>:

  • subagent_aidlc-product-lead-agent (reviewer, ×4 across two stages), each preceded by an empty subagent_response shell ("Response recorded.") that must be dropped.
  • invoke_sub_agent did not appear at all.

The identity is recovered from the **Reviewer:** … self-identification line in tool_response (tool_input is {}), giving Agent Type: aidlc-product-lead-agent in the audit — not unknown. I kept the hook matcher broad (^(subagent_.+|invoke_sub_agent)$) and did the subagent_response exclusion in the adapter gate. This is what I ran on 1.0.89, and it recorded both reviewer completions correctly:

// adapter log-subagent gate — accept both delegate forms, drop the empty shell
const tn = ide.toolName ?? "";
const isSubagentCompletion =
  tn === "invoke_sub_agent" ||
  (tn.startsWith("subagent_") && tn !== "subagent_response");

Fix 2 only works on top of Fix 1 (without the payload there is nothing to match), so both are needed.

Not affected: #456 (runtime-compile)

For completeness: #456 does not regress on 1.0.89. PR #471 fixed it payload-free — gated on the audit tail with an mtime idempotency guard, not on the (empty) tool_input.command — so it does not depend on this channel. In the clean run I saw transition-gate hasTransition=true recompiling after transitions and the idempotency skip when current, with zero audit empty past the first pre-audit moment.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions