How to Debug Kimi-CLI Session Issues Using the Wire File and Session State Files

Kimi-CLI persists every interaction in state.json (high-level metadata) and wire.jsonl (message log), allowing you to inspect session health, trace missing tool calls, and replay conversations for deterministic debugging.

When a Kimi-CLI session behaves unexpectedly—whether it is missing a tool call, losing context, or getting stuck in a loop—the root cause often lies in how session data is persisted. According to the MoonshotAI/kimi-cli source code, the CLI stores session metadata in state.json and maintains a complete message audit trail in wire.jsonl, giving you two complementary entry points to diagnose issues.

Understanding Session Persistence Architecture

Kimi-CLI uses a dual-file persistence model that separates high-level session state from low-level message traffic.

The Session State File (state.json)

Located at <session-dir>/state.json, this file stores a JSON-encoded SessionState model containing high-level metadata such as approval flags, custom titles, todo lists, and archive status. As defined in src/kimi_cli/session_state.py (lines 12-44), this state is managed by the SessionState class and loaded via the load_session_state() function.

The Wire Log (wire.jsonl)

The wire.jsonl file (also in the session directory) acts as a line-delimited JSON log of every WireMessage sent or received. As implemented in src/kimi_cli/wire/file.py (lines 18-26), the first line contains a WireFileMetadata header with the wire-protocol version, followed by WireMessageRecord entries containing timestamps and message payloads.

Step-by-Step Debugging Workflow

To diagnose session issues, inspect both files using the following workflow.

1. Load and Inspect Session State

Call load_session_state(session_dir) to read state.json into a SessionState object. This reveals whether the session thinks it is archived, what todo items are pending, and whether any legacy metadata.json was migrated. According to src/kimi_cli/session_state.py (lines 99-127), this function handles path resolution and migration logic automatically.

2. Read and Analyze the Wire Log

Initialize WireFile(session_dir / "wire.jsonl") to iterate over stored records. Each WireMessageRecord contains the original message (tool calls, UI events, hook requests) plus a monotonic timestamp (src/kimi_cli/wire/file.py, lines 59-66). You can filter for specific event types like ToolCallPart or HookTriggered to trace when specific actions occurred.

3. Correlate Timestamps and Events

Because the wire file stores a monotonic timestamp for each record, you can line up events with high-level state changes captured in state.json. For example, if "plan mode" is missing in the state, check for a HookTriggered event that never arrived in the wire log, indicating a message delivery failure.

4. Detect Corrupted Files

Both persistence layers include error resilience. load_session_state falls back to defaults and logs a warning if state.json is malformed (lines 104-108 in session_state.py). Similarly, WireFile.iter_records() skips unparsable lines while logging failures, allowing you to spot truncated or malformed wire entries (src/kimi_cli/wire/file.py, lines 95-112).

5. Replay Sessions for Reproduction

The KimiSoul runtime can reconstruct sessions from the wire log for deterministic debugging. During replay, the server reads each WireMessage from the file and re-executes the same tool calls (src/kimi_cli/wire/server.py, lines 815-848). This is invaluable for reproducing race conditions or timing-dependent bugs.

Practical Code Examples

Run the following snippets in a Python REPL to inspect a session located at ~/.kimi/sessions/<hash>.

from pathlib import Path
from kimi_cli.session_state import load_session_state
from kimi_cli.wire.file import WireFile

# ----------------------------------------------------------------------

# 1️⃣ Load the high‑level session state

# ----------------------------------------------------------------------

session_dir = Path.home() / ".kimi" / "sessions" / "<hash>"
state = load_session_state(session_dir)
print("Session title:", state.custom_title or "(none)")
print("Todo items:", [(t.title, t.status) for t in state.todos])
print("Archived:", state.archived, "at", state.archived_at)
print("Wire protocol version:", state.wire_mtime)

# ----------------------------------------------------------------------

# 2️⃣ Iterate over the wire log

# ----------------------------------------------------------------------

wire = WireFile(session_dir / "wire.jsonl")
print("\nWire file header protocol version:", wire.version)

# Print the first few messages with timestamps

import itertools
for record in itertools.islice(wire.iter_records(), 10):
    msg = record.to_wire_message()
    print(f"{record.timestamp:.3f}s – {type(msg).__name__}: {msg}")

# ----------------------------------------------------------------------

# 3️⃣ Find a specific type of event (e.g., a tool call)

# ----------------------------------------------------------------------

from kimi_cli.wire.types import is_wire_message, ToolCallPart

matches = []
for rec in wire.iter_records():
    msg = rec.to_wire_message()
    # ToolCallPart is a payload for a tool request; adjust as needed

    if hasattr(msg, "tool_call"):
        matches.append((rec.timestamp, msg.tool_call))
        if len(matches) >= 5:
            break

print("\nFirst few tool calls:")
for ts, tc in matches:
    print(f"{ts:.3f}s → {tc}")

# ----------------------------------------------------------------------

# 4️⃣ Detect malformed lines (helpful when the wire file is corrupted)

# ----------------------------------------------------------------------

for rec in wire.iter_records():
    # The iterator skips bad lines, but we can count them via logging

    pass
print("\nIf you saw warnings in the console, those lines were unparsable.")

Summary

  • Session state is stored in state.json and managed by the SessionState class in src/kimi_cli/session_state.py, tracking metadata like todos and archive status.
  • Message history is logged to wire.jsonl via the WireFile class in src/kimi_cli/wire/file.py, with each record containing a timestamp and payload.
  • Debugging workflow involves loading the state, iterating over wire records with iter_records(), correlating timestamps, and checking for corruption warnings.
  • Session replay is supported by KimiSoul in src/kimi_cli/wire/server.py, enabling deterministic reproduction of issues from the wire log.

Frequently Asked Questions

Where does Kimi-CLI store session files?

Session files are stored in ~/.kimi/sessions/<hash>/, where <hash> is a unique session identifier. Each session directory contains both state.json (metadata) and wire.jsonl (message log).

What does the wire protocol version indicate?

The wire protocol version is stored in the WireFileMetadata header (first line of wire.jsonl) and indicates the schema version of the message format. The SessionState object tracks the last modification time of the wire file in its wire_mtime field to detect external changes.

How do I recover from a corrupted state.json file?

The load_session_state() function automatically falls back to default values and logs a warning if state.json is malformed or missing. You can manually delete the file to force regeneration, though you will lose custom titles and todo items. Archived sessions can be restored by toggling the archived flag back to false in the regenerated state.

Can I manually edit wire.jsonl to fix session issues?

While technically possible, manual editing is not recommended because the file uses a strict line-delimited JSON format with monotonic timestamps. Corrupted entries will be skipped by WireFile.iter_records(), but may cause the replay functionality in src/kimi_cli/wire/server.py to fail. Use the replay feature instead to recreate sessions programmatically.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →