MoonshotAI / MoonshotAI/kimi-cli

Bug: macOS screenshot thumbnails dropped into terminal fail to attach (TemporaryItems race)

Open
#2,182 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
11.4k
Forks
1.3k
Avg merge
9h 47m
Merged PRs (30d)
2

Description

Environment
• OS: macOS (any version with screenshot drag-to-thumbnail)
• Kimi CLI version: 1.40.0
• Install method: uv tool install kimi-cli
Reproduction

  1. Take a macOS screenshot (Cmd+Shift+4) but don't save it — drag the floating thumbnail directly into the Kimi CLI t
    inal.
  2. The path appears in the prompt, e.g.:
    /var/folders/hx/.../TemporaryItems/NSIRD_screencaptureui_abc123/Screenshot 2026-05-07 at 5.47.51 PM.png
  3. Submit the message.
  4. Result: The agent calls ReadMediaFile and gets:
    Error: File not found — /var/folders/hx/.../Screenshot ... PM.png
    By the time the tool runs, macOS has already reaped the TemporaryItems/NSIRD_* directory (cleanup window is ~1–3 s
    nds).
    Root cause
    The image path is read lazily (via ReadMediaFile tool call inside the agent loop), but the path lifecycle is eager (≤
    3 seconds). The two clocks don't agree.
    Claude Code CLI and Cursor solve this the same way: scan the user's input string at submit time, detect image-extensi
    on paths, read them synchronously before the message goes to the model, and attach the bytes as multimodal content bl
    ocks. The agent never sees a path it has to chase.
    Fix
    The fix is localized to the input-handling module (kimi_cli/ui/shell/placeholders.py) plus a small catch-block in the
    shell event router.
    What changed
    File Change
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    kimi_cli/ui/shell/placeholders.py Added synchronous image-path scanning in PromptPlaceholderManager.resolve_comma
    nd(): detects .png/.jpg/.jpeg/.webp/.gif/.heic/.bmp/.svg paths in literal text,
    reads them at submit time, validates size (≤ 20 MB), sniffs MIME from magic byt
    es, and attaches ImageURLPart content blocks.
    kimi_cli/ui/shell/init.py Catches ImagePathResolutionError in _route_prompt_events() and prints a user-fa
    cing error before resuming the prompt loop.
    kimi_cli/ui/shell/prompt.py Threads model_capabilities into PromptPlaceholderManager so image attachment is
    skipped when the model doesn't support image_in.
    tests/ 26 tests covering drag-and-drop, spaces, quotes, Unicode, ~, relative paths, de
    duplication, size cap, missing files, non-image extensions, URLs, and capabilit
    y gating.
    Key behaviors
    • Drag macOS screenshot thumbnail → image visible to model on first turn, zero tool calls.
    • Path with spaces / quotes / Unicode / ~ → all resolve correctly.
    • Two images in one message → both attached as separate content blocks.
    • Same path twice → deduplicated to one attachment.
    • 50 MB image → rejected with inline size-cap error.
    • Missing explicit path (e.g., reaped temp file) → clear error: "Image at was no longer accessible (macOS Temp
    aryItems likely reaped); save to a persistent location and try again."
    • .txt / .pdf / .json path → NOT auto-read; flows through normal text path.
    https://... URL → left as text, not treated as a missing local file.
    • file:///path/to/image.png → stripped to /path/to/image.png and attached.
    Patch
Click to expand full patch

NOTE: This patch is a summary of the exact changes applied.

The three core files are:

- kimi_cli/ui/shell/placeholders.py (~+270 lines including helpers & tests)

- kimi_cli/ui/shell/init.py (+1 import, +4 lines catch block)

- kimi_cli/ui/shell/prompt.py (+2 lines pass model_capabilities)

Because the installed wheel is not a git checkout, a literal git diff is not

available, but the implementation is a straightforward addition to

PromptPlaceholderManager.resolve_command() with these helpers:

_expand_image_path() — unescape spaces, expand ~, strip file://

_find_image_path_candidates() — regex + backward walk to detect image paths

_is_likely_text() — reject text files saved with image extensions

_read_image_sync() — size check, magic-byte MIME, base64 encode

_resolve_image_paths_in_text()— orchestrate detection → ImageURLPart

ImagePathResolutionError — user-facing exception for missing/bad files

Tests
All 26 new tests pass:
platform darwin -- Python 3.13.12, pytest-9.0.3
collected 26 items

test_placeholders.py::TestFindImagePathCandidates::test_basic_absolute PASSED
test_placeholders.py::TestFindImagePathCandidates::test_escaped_spaces PASSED
test_placeholders.py::TestFindImagePathCandidates::test_quoted_paths PASSED
test_placeholders.py::TestFindImagePathCandidates::test_no_candidates PASSED
test_placeholders.py::TestFindImagePathCandidates::test_path_with_spaces PASSED
test_placeholders.py::TestExpandImagePath::test_unescape_spaces PASSED
test_placeholders.py::TestExpandImagePath::test_tilde PASSED
test_placeholders.py::TestReadImageSync::test_valid_png PASSED
test_placeholders.py::TestReadImageSync::test_svg PASSED
test_placeholders.py::TestReadImageSync::test_size_cap PASSED
test_placeholders.py::TestReadImageSync::test_fake_png PASSED
test_placeholders.py::TestReadImageSync::test_missing_file PASSED
test_placeholders.py::TestResolveImagePathsInText::test_valid_png PASSED
test_placeholders.py::TestResolveImagePathsInText::test_two_images PASSED
test_placeholders.py::TestResolveImagePathsInText::test_duplicate_path PASSED
test_placeholders.py::TestResolveImagePathsInText::test_missing_explicit_path PASSED
test_placeholders.py::TestResolveImagePathsInText::test_missing_simple_filename_treated_as_text PASSED
test_placeholders.py::TestResolveImagePathsInText::test_text_not_auto_read PASSED
test_placeholders.py::TestResolveImagePathsInText::test_quoted_path PASSED
test_placeholders.py::TestResolveImagePathsInText::test_escaped_spaces PASSED
test_placeholders.py::TestResolveImagePathsInText::test_relative_path PASSED
test_placeholders.py::TestResolveImagePathsInText::test_unicode_filename PASSED
test_placeholders.py::TestResolveImagePathsInText::test_url_left_as_text PASSED
test_placeholders.py::TestPromptPlaceholderManager::test_resolve_command_with_image PASSED
test_placeholders.py::TestPromptPlaceholderManager::test_resolve_command_with_pasted_text_and_image PASSED
test_placeholders.py::TestPromptPlaceholderManager::test_capability_gating_no_image_in PASSED

============================== 26 passed in 0.51s ==============================
Prior art
• Claude Code CLI ships this exact behavior. Drag a macOS screenshot thumbnail directly into the prompt and it works
the first turn — no ReadMediaFile, no TemporaryItems race.
• Cursor implements the same for Finder drag-and-drop.
Impact
This is table-stakes UX for any terminal-based coding agent on macOS. The screenshot-thumbnail workflow is too common
to lose to a 1-second cleanup race.
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
I'm happy to open this as a proper PR if the maintainers prefer — just let me know and I'll fork + branch.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with kimi_cli/ui/shell/placeholders.py and trace PromptPlaceholderManager.resolve_command(), then review the related routing in kimi_cli/ui/shell/init.py and capability wiring in kimi_cli/ui/shell/prompt.py. Run the 26 tests listed for test_placeholders.py; done means macOS temporary image paths are attached before model handling, errors are user-facing, and capability gating remains covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli, testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.