aws-samples / aws-samples/sample-kiro-coding-metrics-collector

[Bug] AI attribution reports 0 with no signal that detection missed

Open
#3 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
2
Forks
2
PR merge metrics
No merged PRs in 30d

Description

## Environment

- **OS**: Windows 10/11 (reproduced); the code paths discussed are platform-independent
- **Plugin version**: `git-ai-kiro` 0.2.3
- **git-ai engine**: 1.2.6 (`authorship/3.0.0`)
- **Kiro IDE version**: recent build — version sensitivity is the main topic of this report
- **Workspace structure**: single git project (``, branch `dev`)

## Summary

A commit whose content was entirely AI-generated was reported as `ai_additions = 0` / `human_additions = 78`. Every stage of the pipeline completed normally, so there was no error, no user-visible warning, and nothing in the uploaded payload to indicate that AI detection had not produced any data. From the Dashboard's perspective the commit is indistinguishable from one a developer typed by hand.

The immediate trigger was an AI edit that did not go through Kiro's built-in chat. Beyond that specific case, we think there is a more general issue worth raising: AI detection depends on several hard-coded assumptions about Kiro's internal storage layout and execution-log schema. Those assumptions are not version-guarded or validated at startup, and when one of them no longer holds the code path degrades to `console.warn` + `return`. The observable result — for the user and for the Dashboard — is the same as "this developer wrote no AI code".

## Symptom

1. Generated a ~80-line Python file with AI in a repo with the plugin installed.
2. `git commit`.
3. Dashboard shows: human_additions 78 / ai_additions 0 / AI ratio 0.0%.

Expected those lines to be attributed to AI. All 78 were attributed to human, with no indication that anything had gone wrong.

## Evidence chain

Collected with the repo's own `scripts/collect-diagnostics.ps1` and analysed following the repo's own `skills/kiro-coding-metrics-plugin-support` (v3.4.0) methodology.

**1. Plugin and binaries present** — `git-ai.git-ai-kiro-0.2.3`; `bin/` contains `git-ai.exe` (20 MB), `git-ai` (15 MB), `git-ai-linux` (14 MB), `curl.exe`. Rules out the missing-binary failure mode.

**2. Hooks installed and executed** — `.git/hooks/pre-commit` (344 B) and `.git/hooks/post-commit` (11 442 B), both installed by the plugin; post-commit ran to completion (it produced the upload record in item 3). Rules out hooks not installed or not triggered.

**3. Upload payload confirms 0** — last `[stats]` entry in `.git/ai/last_upload_payload.json`:

```json
{
"commit_stats": {
"human_additions": 78,
"ai_additions": 0,
"ai_accepted": 0,
"total_ai_additions": 0,
"mixed_additions": 0,
"git_diff_added_lines": 78,
"tool_model_breakdown": {}
}
}
```

The upload path is healthy; the value itself is 0 and `tool_model_breakdown` is empty. The Dashboard is rendering the data faithfully.

**4. Git note contains no AI prompts** — `git notes --ref=ai show `:

```json
{
"schema_version": "authorship/3.0.0",
"git_ai_version": "1.2.6",
"prompts": {}
}
```

`prompts: {}` — there was nothing AI-attributable at attribution time, which moves the fault upstream of stats computation.

**5. Only a Human checkpoint exists** — `.git/ai/post_commit_debug.log` for this commit:

```
commit=
parent=
va_files=[]
checkpoints=["kind=Human entries=[\"ai_stats_demo.py(la=0,a=0)\"]"]
to_authorship_log:
attr_keys=[]
committed_hunks_keys=[]
```

One `kind=Human` checkpoint (from `pre-commit`'s `git-ai checkpoint human`), no `kind=AiAgent` checkpoint, no virtual-attribution files, no attribution keys. Given only a Human checkpoint, git-ai attributing all 78 lines to a human is the correct behaviour — the input was incomplete.

**6. Working logs empty** — `.git/ai/working_logs/` contains no `/` directory, no `INITIAL`, no `checkpoints.jsonl`, consistent with item 5: `callCheckpointAgentV1` was never invoked for this edit.

### Pipeline localisation

```
[stage 6] Dashboard AI=0 ← faithful render
[stage 5c] upload ai_additions=0 ← evidence 3, faithful upload
[stage 5b] git note prompts:{} ← evidence 4, nothing attributable
[stage 4] working_logs empty ← evidence 6, no INITIAL
[stage 3] no AiAgent checkpoint ← evidence 5, callCheckpointAgentV1 never fired
[stage 1] SessionLogWatcher never saw an Accepted AI write action ◀── break
[stage 0] edit not present in the monitored execution log
```

The break is between stage 0 and stage 1, in detection. Everything downstream worked as designed and faithfully reported that no AI attribution data existed.

## Analysis

### (a) Immediate trigger: AI edits outside the IDE chat path are not observed

In our repro, the code was written by an agent reaching the editor through an external agent interface (session context `Machine ID: acp-client`) rather than Kiro's built-in Vibe/Spec chat. `SessionLogWatcher` only learns about edits that land in Kiro's execution log, so this edit was never observed, no `AiAgent` checkpoint was created, and the commit was scored as fully human.

We appreciate that AI edits arriving through external agents may be out of scope today. Our concern is narrower: when such an edit is not observed, the result is a specific number (78 human lines) rather than a gap. As agent-driven editing becomes more common (ACP clients, CLI agents, MCP-driven editors), the `human_additions` column accumulates measurement error that neither a developer nor an admin has a way to notice.

### (b) Detection assumptions are version-sensitive, and failures are not surfaced

Each of the following is an assumption about Kiro's internal on-disk layout, currently without a version check, startup validation, or user-visible failure path:

| # | Assumption | Location | Behaviour if Kiro changes it |
|---|---|---|---|
| 1 | Agent dir is exactly `.../Kiro/User/globalStorage/kiro.kiroagent` | `sessionLogScanner.ts` → `resolveAgentDir()` | `fs.access` fails → `console.warn("Agent Dir does not exist, SessionLogWatcher not starting")` → watcher never starts, extension keeps running, subsequent commits are attributed to human |
| 2 | Execution logs live in subdirectories whose names start with `414d` | `sessionLogWatcher.ts` → `start()`: `if (!subEntry.startsWith("414d")) continue;` and `watchParentDir()`: `if (!filename \|\| !filename.startsWith("414d")) return;` (same filter in `sessionLogScanner.ts` → `scanForAIEdits()`) | Logs under a differently-named directory are neither watched nor scanned. `start()` still logs "SessionLogWatcher started with N watcher(s)" — N may be 0 — and returns normally |
| 3 | Session IDs come from `workspace-sessions//sessions.json`, shaped as an array of `{sessionId: string}` | `sessionLogScanner.ts` → `getWorkspaceSessionIds()` + `sessionLogParser.ts` → `parseSessionsJson()` | Returns an empty set, after which every parsed log is dropped by the `sessionId mismatch` filter in `processExecutionLog()` |
| 4 | A log is either Format A (non-empty `actions[]`, entries with `actionState === "Accepted"`) or Format B (`context.messages[]` with paired `toolUse` / `toolUseResponse`) | `sessionLogParser.ts` → `parseExecutionLog()` | A third shape parses to `{writeActions: [], format: "B", chatSessionId: undefined}` → `processExecutionLog()` logs `Skipped (no chatSessionId)` and drops the edit |
| 5 | Kiro appends to the execution log incrementally, before the user commits, and growth is detectable via file size | `sessionLogWatcher.ts` → `snapshotExistingFiles()` (records sizes and deliberately does not process pre-existing files) + `checkAndProcess()` (`if (lastSize === stat.size) return;`) | If Kiro writes once at turn/session end, writes via temp-file + atomic rename (new inode, which `fs.watch` on the watched dir may not report), or produces a same-size rewrite, checkpoints are missed or arrive after the commit |
| 6 | Edits are recorded in an execution log at all | detection design as a whole | External / ACP / CLI agent edits are not observed — see (a) |

Assumption 2 is the one we'd flag first: `414d` is an undocumented prefix over an opaque hash namespace, and nothing in Kiro's public surface commits to keeping it, or to keeping execution-log directories one level below the workspace-hash directory. Today AI detection depends on that four-character match.

### Additional observation: at least three on-disk shapes exist, two are handled

Inspecting a real Kiro agent directory (macOS, `Kiro.app` 1.0.242) we found two structurally different file shapes side by side inside the same workspace-hash directory.

Under `/414d…/` — the Format A shape the parser expects:

```
keys: executionId, workflowType, status, startTime, input, autonomyMode,
chatSessionId, actions, context, result, endTime, usageSummary,
contextUsagePercentage
context: dict actions: list(9) chatSessionId: present
```

Directly under `/`, ~10 900 `*.chat` files with a shape that matches neither A nor B:

```
keys: executionId, actionId, context, validations, chat, metadata
context: list (not a dict → `context.messages` lookup fails)
chat: list(11)
chatSessionId: absent actions: absent
```

Fed to `parseExecutionLog()`, that third shape yields zero write actions and an undefined `chatSessionId`, so `processExecutionLog()` discards it at the `Skipped (no chatSessionId)` branch — externally indistinguishable from "the user made no AI edits".

To be clear, we are not claiming `*.chat` is the current execution-log format; on our machine those files predate the `414d*` logs. The narrower observation is that Kiro's agent storage already contains more shapes than the parser recognises, and the parser currently has no way to distinguish "unknown schema" from "no AI activity".

### Open question: write timing

We could not determine whether current Kiro still flushes execution-log content before the user's `git commit`, or whether it batches / atomically rewrites at turn end. This matters for assumption 5 and for the checkpoint-before-commit ordering generally. It may be worth pinning down per Kiro version and recording it alongside the layout and schema expectations, since a timing change would produce the same result as a layout change.

## Impact

- A commit that is entirely AI-generated can be reported as entirely human, and nothing distinguishes a genuine 0% AI result from a detection miss.
- The error has a consistent direction: every detection miss adds to `human_additions`, so aggregate views over-report human authorship and under-report AI — the quantity the project exists to measure.
- At team scale it is difficult to notice: an admin looking at the Dashboard cannot tell a team that genuinely writes code by hand from a team whose detection stopped working after a Kiro update.
- Because assumptions 1–5 are not version-guarded, a Kiro update alone could reduce AI detection to zero with no code change and no alert on this side.

## Suggestions

Roughly in order of value-to-effort, and offered as suggestions rather than a required design.

**1. Surface detection failures instead of falling back to "all human".**
An explicit detector-health state carried into the uploaded payload would cover most of this report, e.g. `detector_status: "healthy" | "no_agent_dir" | "no_watch_targets" | "no_session_ids" | "unknown_schema"`, plus counters (`watchers_installed`, `logs_parsed`, `logs_dropped_unknown_schema`, `logs_dropped_session_mismatch`). Then:
- The Dashboard could render commits with a non-healthy detector as *unattributed* rather than human, so a detection miss is never scored as human authorship.
- The status bar could show a warning state when `watchers_installed === 0` or the drop counters are non-zero.
- `start()` could treat "0 watchers installed" as an error condition rather than logging `started with 0 watcher(s)` and continuing.

**2. Reduce reliance on the `414d` prefix.**
Discovering execution-log directories by content probing (a directory containing files that parse to a known schema) would be more robust than matching on name. A smaller step: make the prefix a configuration value with a documented default, and log a distinct warning when a workspace-hash directory has subdirectories but none match — that situation is a good signal that Kiro's layout has changed.

**3. Make schema handling explicit and forward-tolerant.**
Having `parseExecutionLog()` return a discriminated result — `{kind: "formatA" | "formatB" | "unknown", ...}` — would let `unknown` be counted, logged with the observed top-level keys, and surfaced via (1), rather than coerced to `format: "B"` with an empty action list. Regression fixtures for each known shape, including the `*.chat` shape above, would catch schema drift in CI.

**4. Document a version compatibility matrix, and add a self-check command.**
Recording the tested Kiro IDE versions against agent-dir layout, log schema, and write timing would make the supported range explicit. A `git-ai-kiro: Run detector self-check` command that resolves the agent dir, enumerates candidate log directories, parses the most recent log, and reports which of assumptions 1–5 currently hold would turn "AI shows 0 — is it broken, or did I not use AI?" into one command instead of a diagnostics-bundle investigation.

**5. Consider a signal that doesn't require tracking IDE internals.**
An explicit checkpoint API/CLI that any agent (built-in chat, ACP client, CLI agent, MCP-driven editor) can call to declare "I wrote lines X–Y of file F" would complement execution-log watching. The log watcher stays as the zero-config path; the explicit path makes agent-driven workflows attributable and is unaffected by IDE layout changes.

**6. Document the current limitation.**
The README lists "AI coding via vibe coding or spec mode" as supported, but does not mention that AI edits arriving through other channels are counted as human without a warning. Adding that to the README and plugin description would help before any code changes land.

## What we'd consider a fix

- With the agent dir renamed or absent, a commit made after an AI edit is reported as unattributed rather than 100% human, and the user sees a warning.
- With an execution log in an unrecognised schema, the uploaded payload carries a non-zero `logs_dropped_unknown_schema` and the commit is not scored as human.
- A self-check command reports which detection assumptions currently hold, on demand, without collecting a diagnostics bundle.
- Unit fixtures cover Format A, Format B, and at least one unrecognised shape, asserting the unrecognised shape is reported as `unknown` rather than as "no AI activity".

## Reproduction steps

**Path A — external agent edit (what we hit):**
1. Install `git-ai-kiro` 0.2.3 in Kiro; open a git repo; confirm hooks are installed.
2. Have an external agent (ACP client / CLI agent, i.e. not Kiro's built-in chat) write a new ~80-line file into the repo.
3. `git add` + `git commit`.
4. Observe `ai_additions: 0`, `human_additions: 78` in `.git/ai/last_upload_payload.json` and on the Dashboard; `git notes --ref=ai show HEAD` shows `prompts: {}`; `.git/ai/working_logs/` is empty; `post_commit_debug.log` shows a lone `kind=Human` checkpoint. No error is reported anywhere.

**Path B — simulate a Kiro layout change (isolates the detection issue from Path A):**
1. With the plugin installed and working, rename the agent dir `…/globalStorage/kiro.kiroagent` to `…/globalStorage/kiro.kiroagent.bak` and restart Kiro (equivalently: rename a `414d*` execution-log directory).
2. Make an AI edit through Kiro's built-in chat, Accept it, wait a few seconds, then commit.
3. The outcome is the same: all lines attributed to human, `working_logs` empty, and the only trace is a `console.warn` in DevTools.

Path B is the case we'd most like feedback on, since it shows the behaviour is not specific to external agents: a change to any of the assumptions above produces a human-attributed number rather than a detectable failure.

## Notes

- The diagnosis above was produced using this repository's own `kiro-coding-metrics-plugin-support` skill (v3.4.0). Applying that methodology strictly, the incident classifies as "usage/environment, not a code bug" — every stage faithfully reported the absence of AI data. We're filing it anyway because the end-to-end outcome (no AI data detected → 78 human-written lines reported) seems worth addressing at the product level even though no individual stage misbehaved. Happy to be told this is working as intended and belongs in the docs instead.
- We can provide the full `diagnostics.txt`, the raw `post_commit_debug.log` block, and the `*.chat` / `414d*` schema samples (key structure only, contents redacted) if that would help.

Contributor guide

Open the contributing guide

Research direction

Start by reading sessionLogScanner.ts, sessionLogWatcher.ts, and sessionLogParser.ts, especially the paths and branches identified in the report. Reproduce the detector flow with the repository’s scripts/collect-diagnostics.ps1 and inspect how missed or unknown inputs are currently dropped. Done should distinguish detection failures from genuine human-only attribution and define a validated, user-visible failure state.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, typescript
Domain
observability, tooling
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.