Skip to content

fix(kiro-cli): derive the POSIX search dirs from the home that was asked for - #7172

Merged
iamwhatever merged 1 commit into
kirodotdev:mainfrom
leonlaiyc:fix/kiro-cli-dirs-home-passthrough
Sep 7, 2026
Merged

fix(kiro-cli): derive the POSIX search dirs from the home that was asked for#7172
iamwhatever merged 1 commit into
kirodotdev:mainfrom
leonlaiyc:fix/kiro-cli-dirs-home-passthrough

Conversation

@leonlaiyc

@leonlaiyc leonlaiyc commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Problem / Motivation

known_kiro_cli_dirs(platform_name, home, environ) returns the directories where Kiro CLI may be installed. It builds the first entries from its home argument, then appends the augmented inherited PATH:

# kiro_cli.py — POSIX branch
dirs = [str(home / ".local" / "bin"), str(home / ".cargo" / "bin")]
...
dirs.extend(part for part in augmented_path(environ.get("PATH", "")).split(os.pathsep) if part)

augmented_path resolves its own home when the keyword is omitted — resolved_home = home or os.path.expanduser("~") (env.py:661) — and uses it to format _EXTRA_PATH_DIRS ({home}/.toolbox/bin, {home}/.npm-packages/bin, {home}/.volta/bin, {mise_data}/shims) plus the Node bin dirs. So one call returns a list mixing two accounts. The win32 branch three lines above already forwards it: augmented_path("", home=str(home)).

Measured on current main (5fe1d64e4), asking for /home/alice while the process home is elsewhere:

entries derived from the ASKED home  : 2   (.local/bin, .cargo/bin)
entries derived from the PROCESS home: 5   (.toolbox/bin, .npm-packages/bin,
                                            .volta/bin, mise/shims, node bins)

win32 control (already forwards home=): 0 process-home entries

Why it matters

This breaks the exact property the ACP spawn resolver was just built to rely on. acp/client.py's _resolve_kiro_cli_for_spawn says so in its own docstring:

known_kiro_cli_dirs is a pure function of (platform, home, environ), so passing the same mapping here and to the diagnostic guarantees the directories named in a "not found" message are the directories that were actually searched.

On POSIX it is not a pure function of those arguments. A live expanduser("~") sits in the middle, so the resolve and the diagnostic are two readings again — which is precisely the split-read defect #5048 and #6986 fixed at the call sites, surviving one level down. #6986's First Principles review named it:

the new docstring's claim that known_kiro_cli_dirs is "a pure function of (platform, home, environ)" is slightly overstated: its POSIX branch calls augmented_path(environ.get("PATH","")) without home= … mirroring that one argument would close the residual.

It is not only the message. find_kiro_cli_candidates walks this same list, so the directories actually searched for the binary are half-derived from the wrong account whenever the caller's home differs from the process's live ~. Both production callers pin a home up front for exactly this reason — PrerequisiteMonitor captures self._home at construction and probes later on a worker thread (kiro_prerequisite.py:1879, :2533), and _resolve_kiro_cli_for_spawn snapshots per attempt — so the gap between capture and use is real by design, not hypothetical.

Being scoped to POSIX, this is invisible on the Windows branch that was already correct.

What changed (motivation → approach → change)

Symptom → the search-dir list mixes two accounts, so a reported search path can disagree with the search that ran. Root cause → the POSIX branch drops the home= keyword that the win32 branch passes, and augmented_path then falls back to a live expanduser("~"). Change → forward it, mirroring the branch that was already right.

src/kiro_crew/kiro_cli.py, one argument:

-        dirs.extend(
-            part for part in augmented_path(environ.get("PATH", "")).split(os.pathsep) if part
-        )
+        dirs.extend(
+            part
+            for part in augmented_path(environ.get("PATH", ""), home=str(home)).split(os.pathsep)
+            if part
+        )

augmented_path's home keyword already exists and is already documented for this case ("pins user-relative candidates for callers already resolving a specific account instead of whichever account owns the current process"). Nothing is added; an existing seam is used on the branch that was missing it.

The function's docstring now states the purity contract rather than leaving it implied in a caller's docstring, so the next reader of known_kiro_cli_dirs sees the property that the ACP diagnostic depends on. The new code comment says why the keyword is there, naming the caller contract it serves.

Scope. One production line plus its comment and docstring. No behaviour change for win32 (already forwarding), and none for any caller whose home matches the process home — which is why this is a latent-correctness fix rather than a visible-symptom one, and the PR says so rather than claiming a user report.

Tests

New in test/test_env.py, class TestKnownKiroCliDirsIsPureInItsHome — sited next to the existing augmented_path / known_kiro_cli_dirs boundary tests:

  • test_posix_dirs_never_mention_the_process_home — patches expanduser to a distinct process home, asks for another, and asserts no returned entry is derived from the process home. Carries its own control (the asked-for home IS represented) so it cannot pass by the function returning nothing home-relative.
  • test_the_templated_extras_follow_the_asked_home — names .toolbox, .npm-packages, .volta individually and requires each entry to start with the asked-for home. .local/bin alone would not discriminate: the POSIX branch always built that one correctly, so a partial fix would pass a looser assertion.
  • test_win32_branch_still_pins_its_home — the control on the branch that was already correct, showing the fix mirrors an existing convention rather than inventing one.
  • test_two_calls_with_the_same_arguments_agree_across_a_home_change — the property the ACP diagnostic needs, stated directly: same arguments, process home moved in between, same result.

Red-before, production change reverted with the tests in place: 3 failed, 1 passed (the win32 control passes on both sides, as it must).

AssertionError: these entries came from the process home, not the home this call
                was given: [...process-home/.local/bin, ...process-home/.toolbox/bin,
                ...process-home/.npm-packages/bin, ...mise/shims, ...volta/bin]
AssertionError: .toolbox was derived from '...process-home\.toolbox\bin', not from
                the home this call was given
AssertionError: the same arguments produced different search directories after the
                process home moved, so a reported search path can disagree with the
                search that ran

Green after, test_env.py + test_acp_client.py + test_kiro_prerequisite.py (Python 3.10.6, Windows):

failed passed
pristine origin/main, twice 18 785
this branch 18 789

Exactly the four new tests, no flips in either direction. The 18 are a stable Windows-only local baseline reproduced on pristine main — TestResolveKrb5Ccname (POSIX os.getuid) and two path-separator assertions in TestAugmentedPath / TestExtraMcpPathDirs. test_acp_client.py + test_kiro_prerequisite.py alone are 677 passed, 50 skipped, 0 failed on this branch.

Gates green: flake8, isort, scripts/check_black_formatting.py, mypy on the changed module (no issues in it).

Manual verification

N/A — unit coverage sufficient: the defect is which account a returned directory string is derived from, which the tests observe directly by pinning two different homes. Reproducing it by hand needs a gateway whose process home differs from the account being resolved, on POSIX.

Related Issues

Residual named by the First Principles review on #6986 (merged). Same split-read class as #5048 (merged).

no linked issue: both prior issues in this defect class are already closed; this PR fixes the residual their reviews named, which was never filed as its own issue.

Pattern harvest

Rule candidate: review-prompt — when a function takes an environ/home argument, flag any code path inside it (including helpers it calls, like augmented_path without home=) that falls back to live os.environ / os.path.expanduser("~") reads. This is the third instance of the split-read class (#5048, #6986, here), each one level deeper in the call chain.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

@leonlaiyc
leonlaiyc requested a review from a team as a code owner August 31, 2026 02:07
@leonlaiyc
leonlaiyc requested a review from patrigao August 31, 2026 02:07
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Aug 31, 2026
@leonlaiyc

Copy link
Copy Markdown
Contributor Author

If Backend Tests (3.10, 1) goes red here too, it is the same unattributable main-side failure described in #7176: test_agent_spec_hardened_reads.py still asserts _resolve_mcp_server's pre-#2602 return shape, and reproduces on origin/main unpatched. This PR touches only kiro_cli.py and test_env.py.

@leonlaiyc

Copy link
Copy Markdown
Contributor Author

Full CI triage on this head — two failures, neither attributable to the diff. This PR touches only kiro_cli.py (POSIX branch) and test_env.py.

  1. test_agent_spec_hardened_reads.py::TestResolveMcpServer::test_valid_agent_spec_still_resolves_under_the_same_capassert (('c','a'), {}) == ('c','a'). The main-side breakage described in test(cron): pin the env half of the post-#2602 resolver contract #7176: fix(cron): forward per-server env, sanitized, and pin wrappers at their source #2602 changed _resolve_mcp_server to (argv, env) and updated two of the three asserting test files. Reproduces on origin/main unpatched, and fails identically on fix(dashboard): serialize the auto-update toggle against both config writers #7167, whose diff is unrelated.
  2. test_cli_logging.py::TestDrainBeforeHardExit::test_no_listener_is_a_silent_no_opassert <QueueListener object ...> is None. A listener left installed by an earlier test in the same shard; nothing in this diff touches logging teardown or the drain path.

No SHA churn to reroll.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Aug 31, 2026
@dwu96 dwu96 added the needs-pr-triage PR scanner: awaiting automated triage label Sep 7, 2026
@NicholasRBowers NicholasRBowers added drive-to-green PR claimed by drive-to-green pipeline and removed needs-pr-triage PR scanner: awaiting automated triage labels Sep 7, 2026
@NicholasRBowers

Copy link
Copy Markdown
Contributor

🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: This PR has been inactive for 7+ days with failing CI. I've assessed the blockers and they appear resolvable — I'll push fixes directly to this branch as a co-author.

Assessment: All 3 failing tests (test_agent_spec_hardened_reads.py, test_cli_logging.py, test_resource_limits_schema.py) are in files this one-line diff never touches, on an Aug-31 base — stale-base drift. Plan: rebase onto current main, re-verify gates, force-push to re-fire CI and the fork review lanes.

If you'd prefer I don't touch this PR, add the pr-no-autofix label.

@NicholasRBowers
NicholasRBowers force-pushed the fix/kiro-cli-dirs-home-passthrough branch from a14af88 to b9bf504 Compare September 7, 2026 03:11
@NicholasRBowers

Copy link
Copy Markdown
Contributor

🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]

Drive-to-green update — rebase-only push, no code changes.

  • Rebased fix/kiro-cli-dirs-home-passthrough onto current main (064e2fae0). The rebase was clean; the diff is byte-identical to Leon's original change (forward home= on the POSIX branch of known_kiro_cli_dirs + docstring/purity-contract notes + 4 tests in test/test_env.py).
  • The previous head's CI reds (Backend Tests (3.10, 1), (3.12, 1), two Windows shards, Coverage Gate) were base drift: the failing test (test/test_agent_spec_hardened_reads.py::TestResolveMcpServer::test_valid_agent_spec_still_resolves_under_the_same_cap) is in a file this diff never touches, from an Aug-31 run against a stale base. Post-rebase it passes locally: 195 passed across test/test_env.py + test/test_agent_spec_hardened_reads.py.
  • Local gates on the new head: isort ✅, flake8 ✅, mypy (1305 files, 0 issues) ✅.
  • Head is now b9bf504b6 — single commit, Leon's authorship preserved, Co-authored-by: Kiro Crew trailer added.

The new head re-fires CI; on green the fork review lanes (GPT/Opus/Design/First Principles/UX) will dispatch. Monitoring at 5-minute intervals.

@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review (fork) — ✅ no blocking findings

Reviewed 7b919c2e4c57304358f49960e7cf5ade4201dd8f via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 7b919c2

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review (fork) — ✅ no blocking findings

Reviewed 7b919c2e4c57304358f49960e7cf5ade4201dd8f via the fork AI-review pipeline; updated in place on each push.

Review details

The change is a straightforward correctness fix: the POSIX branch now forwards home=str(home) to augmented_path, matching the win32 branch, so home-relative entries derive from the argument rather than a live expanduser("~"). No defect is introduced — no new crash, removed guard, or security regression. The discovery pass found no candidates, and I find nothing groundable to the required bar.

No findings.

[OPUS-REVIEWED] 7b919c2

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5, fork) — ✅ PASS

Premise-level review of 7b919c2e4c57304358f49960e7cf5ade4201dd8f via the fork AI-review pipeline — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All verification is done. Every load-bearing claim in the PR holds in the trusted base: the POSIX branch omits home= while win32 forwards it (kiro_cli.py:212 vs :215), augmented_path falls back to a live expanduser("~") (env.py:661), the ACP callers' docstrings already assert the purity contract this makes true (acp/client.py:348, :416), and both production callers pin a home (kiro_prerequisite.py:1829, acp/client.py:438). I grepped augmented_path\( across src: the other 11 call sites all read live os.environ with no pinned home in scope, so there are no unfixed siblings. One wrinkle: the new comment cites _resolve_kiro_cli_for_spawn, which greps to 0 matches in the repo — the real symbol is _resolve_kiro_bin (acp/client.py:335).

First-Principles-Verdict: PASS

A one-argument fix at cause level: the purity contract two callers already rely on becomes true instead of only asserted.

What this change ships

Intent: make the POSIX CLI search derive every home-relative directory from the home the caller asked for — a FIX.

  1. On POSIX with a pinned home, the search and its "not found" list now name one account's dirs — justified, cause-level
  2. known_kiro_cli_dirs docstring now states the purity contract its callers depend on — justified
  3. A 9-line call-site comment restating that same contract — duplicate of the docstring above it
  4. Four tests pinning home-purity on both branches, with red-before evidence — justified

The contract has 2 real consumers (kiro_prerequisite.py:1829–1842 pins home at construction; acp/client.py:438 passes the caller's home per the #5048 guarantee). Sibling count: grepped augmented_path\( — 11 other call sites omit home=, all read live os.environ with no pinned home in scope, so the process account is the intended one there; 0 unfixed siblings.

Subtractions

  • Drop the call-site comment block above kiro_cli.py's POSIX dirs.extend — it restates the docstring added three lines up on the same function, and its anchor _resolve_kiro_cli_for_spawn does not exist (grep: 0 matches; the base symbol is _resolve_kiro_bin, acp/client.py:335). The docstring alone carries the contract; a dangling pointer will only diverge further.

[FIRST-PRINCIPLES-REVIEWED] 7b919c2

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5, fork) — ✅ PASS

Design-level review of 7b919c2e4c57304358f49960e7cf5ade4201dd8f via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

The fix checks out end-to-end: the base code at kiro_cli.py:215 omits the home= keyword the win32 branch forwards, augmented_path falls back to a live expanduser("~") at env.py:661, and the ACP resolver docstrings (acp/client.py:348, :416) assert exactly the purity this breaks. The change uses the existing seam, mirrors the already-correct branch, and the tests pin the property directly. The one residual (live MISE_DATA_DIR/XDG_DATA_HOME reads in mise_data_dir) is documented in the new docstring; env overrides are process-stable, so it's below concern level — though the acp/client.py purity claim stays slightly overstated.

Design-Verdict: PASS

A one-argument fix at the true root cause, using an existing documented seam, mirroring the branch that was already correct, with property-pinning tests.

Suggestions

  • The purity claims in acp/client.py:348 and :416 remain slightly overstated (mise shim entry still reads live MISE_DATA_DIR/XDG_DATA_HOME); add the same one-line caveat the new known_kiro_cli_dirs docstring carries, so the next split-read hunt starts at mise_data_dir instead of rediscovering it.

[DESIGN-REVIEWED] 7b919c2

…ked for

`known_kiro_cli_dirs` builds `~/.local/bin` and `~/.cargo/bin` from its
`home` argument, then appends `augmented_path(...)` for the inherited
PATH. On POSIX it omitted `home=`, and `augmented_path` falls back to a
LIVE `os.path.expanduser("~")` — so one call returned a directory list
mixing two accounts: the caller's home for the first entries, and
whichever account owns the process for the `{home}`-templated extras
(`.toolbox/bin`, `.npm-packages/bin`, `.volta/bin`, the mise shims) and
the Node bin dirs. The win32 branch already forwards `home=`.

That breaks the property the ACP spawn resolver relies on. It pins one
`(platform, home, environ)` reading and passes it to both the resolve and
the "kiro-cli not found (searched ...)" diagnostic precisely so the
directories named are the directories walked; a live re-read in the
middle lets the two disagree. It is the split-read class kirodotdev#5048 and kirodotdev#6986
fixed at the call sites, surviving one level down — and kirodotdev#6986's own
review named it.

Forward `home=` on the POSIX branch, mirroring win32, and state the
purity contract in the docstring instead of leaving it implied.

Original fix authored by Leon (leonlaiyc). Rebased onto current main to
clear base-drift CI failures; no code changes in the rebase.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
@NicholasRBowers
NicholasRBowers force-pushed the fix/kiro-cli-dirs-home-passthrough branch from b9bf504 to 7b919c2 Compare September 7, 2026 03:31
@NicholasRBowers

Copy link
Copy Markdown
Contributor

🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]

self-added: no

  • span=f1581dc8598e — fixed: docstring overstated the purity guarantee

The finding holds: mise_data_dir() (env.py:261) reads live MISE_DATA_DIR/XDG_DATA_HOME from os.environ, so "nothing here reads the live process environment" was false as written.
Fixed in 7b919c2: the docstring now scopes the guarantee to home-derived paths — every home-derived entry comes from the home argument, never a live expanduser("~") — and names the mise_data_dir override reads explicitly as the exception, so the ACP-diagnostic contract statement stays but no longer overclaims.
This ruling covers the class: findings that the purity wording overstates env isolation for process-level tool overrides (MISE_DATA_DIR, XDG_DATA_HOME) are addressed by the scoped wording.

@NicholasRBowers

Copy link
Copy Markdown
Contributor

🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]

Drive-to-green update — round 2, head b9bf504b67b919c2e4.

Changes in this push:

  1. Docstring fix (the one GPT advisory finding, span f1581dc8598e): the known_kiro_cli_dirs docstring claimed "nothing here reads the live process environment", which mise_data_dir()'s MISE_DATA_DIR/XDG_DATA_HOME reads contradict. The guarantee is now scoped to home-derived paths, with the mise override reads named as the exception. No production-logic change; the home= forwarding fix is untouched.
  2. PR body — ## Pattern harvest added (required by the PR Hygiene gate on fix PRs; this body predated the gate). Filed as a review-prompt rule candidate for the split-read class.
  3. PR body — CLA placeholder section removed (leftover template placeholder).
  4. PR body — explicit no linked issue: line under Related Issues: both prior issues in this class (fix(acp): say which directories a failed spawn actually searched #5048, fix(acp): report the environment the kiro-cli search actually used #6986) are closed; the residual this PR fixes was named in review but never filed.

Local gates on 7b919c2e4: flake8 ✅ isort ✅ black ✅ mypy ✅, test/test_env.py 130 passed. Single commit, Leon's authorship preserved. The old head's in-flight CI run was cancelled before the push; fresh CI is running on the new head.

@github-actions github-actions Bot removed the readiness: action required A blocking check or review needs attention label Sep 7, 2026
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 7, 2026
@NicholasRBowers

Copy link
Copy Markdown
Contributor

🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]

✅ Review-ready at head 7b919c2e4.

  • All 67 checks green; PR Readiness passed; mergeable; 0 unresolved threads.
  • All five AI review lanes PASS with zero findings on this head (GPT, Opus, Design, First Principles, UX).
  • Drive summary: 2 pushes total — (1) rebase onto current main clearing the base-drift CI reds, zero code changes; (2) docstring scoped per the one GPT advisory (span f1581dc8598e, disposition recorded) + body brought up to the PR Hygiene gate (## Pattern harvest, CLA placeholder removed). The production fix is byte-identical to Leon's original one-line home= forwarding; authorship preserved.
  • One infra intervention this round: a concurrency-cancelled duplicate Code Review run was re-run (no code change).

Ready for maintainer review and merge. Auto-merge is not armed.

@iamwhatever
iamwhatever enabled auto-merge (squash) September 7, 2026 06:05

@iamwhatever iamwhatever left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tier 1 auto-approve: fix (2 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: fix -- the POSIX branch of known_kiro_cli_dirs omitted the home= forward to augmented_path, so a single call mixed .local/bin/.cargo/bin from the caller's home with the {home}-templated and Node/mise entries from the process's account, breaking the ACP resolver's "the directories named in a not-found message are the directories that were searched" contract; one missing keyword plus a regression test, no behaviour change elsewhere. CodeQL is not applicable on this fork PR (default-setup emits no check-run); SAST coverage is Semgrep only, latest run success with 0 annotations.

@iamwhatever
iamwhatever merged commit ebbf4a4 into kirodotdev:main Sep 7, 2026
73 of 79 checks passed
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 7, 2026
@dwu96 dwu96 removed the drive-to-green PR claimed by drive-to-green pipeline label Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants