Skip to content

fix(token-stats): resolve test failures and lint errors#1485

Open
UncertaintyDeterminesYou4ndMe wants to merge 11 commits intoMoonshotAI:mainfrom
UncertaintyDeterminesYou4ndMe:feat/token-usage-stats
Open

fix(token-stats): resolve test failures and lint errors#1485
UncertaintyDeterminesYou4ndMe wants to merge 11 commits intoMoonshotAI:mainfrom
UncertaintyDeterminesYou4ndMe:feat/token-usage-stats

Conversation

@UncertaintyDeterminesYou4ndMe
Copy link

@UncertaintyDeterminesYou4ndMe UncertaintyDeterminesYou4ndMe commented Mar 17, 2026

Fix test failures and lint errors for the token usage stats feature.

Changes

  • Remove unused pytest import from test_token_ledger.py
  • Fix _session_start_time AttributeError when tests use object.new
  • Update test_prompt_tips.py assertions to match new status bar design
  • Restore deleted web/package-lock.json

Testing

  • ✅ All tests pass (944 passed, 7 skipped)
  • ✅ Lint (ruff) passed
  • ✅ Type check (pyright) passed

Related

Part of the token usage stats feature.


Open with Devin

陆逊 and others added 3 commits March 17, 2026 19:09
…eekly ledger

- Add TokenStats dataclass to kimi-agent-sdk Session for per-session accumulation
- Add TokenLedger for persistent daily/weekly token stats (~/.kimi/token-stats.json)
- Redesign status bar: Claude Code-style context bar with █/░, token counts, elapsed time
- Show context window usage as percentage + absolute tokens (e.g. 27% (45k/128k))
- Show cumulative session token stats (in/out) in status bar
- Show daily and weekly token totals below main status line
- Add tests for TokenLedger (boundary resets, persistence, resilience)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove unused pytest import from test_token_ledger.py
- Fix _session_start_time AttributeError when tests use object.__new__
- Update test_prompt_tips.py assertions to match new status bar design
- Restore deleted web/package-lock.json
…sage-stats

fix(token-stats): resolve test failures and lint errors
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b19fecd767

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +112 to +115
tmp = self._file.with_suffix(".tmp")
tmp.write_text(json.dumps(data))
tmp.rename(self._file)
except OSError:

Choose a reason for hiding this comment

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

P2 Badge Use replace when persisting token stats file

Switching to tmp.rename(self._file) makes persistence fail on Windows once the destination file already exists, because Path.rename there does not overwrite existing paths. Since the OSError is swallowed, record() silently stops updating token-stats.json after the first write, so daily/weekly totals become stale across sessions. Use Path.replace/os.replace for cross-platform atomic overwrite behavior.

Useful? React with 👍 / 👎.

Comment on lines +1650 to +1652
current_toast_left = _current_toast("left")
if current_toast_left is not None:
tip_text: str | None = current_toast_left.message[:available]

Choose a reason for hiding this comment

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

P2 Badge Render right-position toast messages in bottom toolbar

The redesigned toolbar now only reads _current_toast("left") and never renders the right queue, so notifications queued with position="right" are effectively invisible. This regresses background MCP feedback (e.g. OAuth/connect toasts emitted from toolset._toast_mcp), leaving users without status/error messages that previously appeared in the right span.

Useful? React with 👍 / 👎.

@UncertaintyDeterminesYou4ndMe
Copy link
Author

image

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5cf3112b74

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +72 to +75
# ── public interface ──────────────────────────────────────────────────

def record(self, usage: TokenUsage) -> None:
"""Add *usage* to both daily and weekly buckets, then persist."""

Choose a reason for hiding this comment

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

P2 Badge Recompute period boundaries before recording usage

record() always appends into the day/week keys captured at construction time, so a long-running CLI session that crosses midnight (or a Monday boundary) keeps charging usage to the previous period until the process restarts. That makes the displayed daily/weekly stats incorrect despite the module contract saying boundaries reset automatically.

Useful? React with 👍 / 👎.

except (json.JSONDecodeError, OSError):
return

if data.get("daily", {}).get("date") == self._today_str:

Choose a reason for hiding this comment

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

P2 Badge Validate ledger section types before nested .get access

_load() assumes daily and weekly are mapping objects and immediately calls nested .get(...); if token-stats.json is valid JSON but has an unexpected shape (for example from manual edits or legacy data), this raises AttributeError during startup and prevents the agent from initializing. Since this file is treated as best-effort, malformed-but-parseable shapes should be ignored instead of crashing.

Useful? React with 👍 / 👎.

devin-ai-integration[bot]

This comment was marked as resolved.

- Remove unused pytest import from test_token_ledger.py
- Fix _session_start_time AttributeError when tests use object.__new__
- Update test_prompt_tips.py assertions to match new status bar design
- Restore deleted web/package-lock.json
- Merge upstream/main and resolve import conflicts

Additional fixes from review:
- Fix Windows file persistence: use tmp.replace() instead of tmp.rename()
- Fix swapped color thresholds in _pct_color() (50-69% yellow, 70-89% orange)
- Fix missing right-position toast rendering in status bar

Further fixes:
- Fix TokenLedger long-running session boundary detection (recompute on record/read)
- Fix type validation in _load() to handle malformed JSON gracefully
- Fix StatusSnapshot to always include daily/weekly stats (not None when zero)
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e2a63202c2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +76 to +79
self._maybe_reset_boundaries()
self._daily.add(usage)
self._weekly.add(usage)
self._save()

Choose a reason for hiding this comment

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

P2 Badge Merge persisted counters before overwriting stats file

record() updates only this process's in-memory _daily/_weekly values and then writes them back, but _maybe_reset_boundaries() only checks date keys and never reloads current totals from disk. If two kimi sessions run concurrently, the later writer overwrites the earlier writer's increments (last-write-wins), so “all sessions” daily/weekly totals become undercounted whenever sessions overlap.

Useful? React with 👍 / 👎.

Comment on lines +51 to +54
input_other=d.get("input_other", 0),
output=d.get("output", 0),
input_cache_read=d.get("input_cache_read", 0),
input_cache_creation=d.get("input_cache_creation", 0),

Choose a reason for hiding this comment

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

P2 Badge Coerce loaded ledger fields to integers

from_dict() copies JSON values without numeric validation, so valid-but-untyped data like "input_other": "100" gets stored as a string in _PeriodStats; the next record() then hits TypeError when add() does self.input_other += usage.input_other. Because this file is user-editable/best-effort state, non-int numeric fields should be coerced or rejected during load instead of crashing usage recording.

Useful? React with 👍 / 👎.

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +1705 to +1710
if status.daily_token_usage is not None:
fragments.append(("", "\n"))
fragments.extend(_ledger_line("today", status.daily_token_usage))
if status.weekly_token_usage is not None:
fragments.append(("", "\n"))
fragments.extend(_ledger_line("week", status.weekly_token_usage))
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot Mar 17, 2026

Choose a reason for hiding this comment

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

🟡 Daily/weekly token stats always shown in toolbar (even when zero) due to is not None check on always-non-None value

The TokenLedger.daily and TokenLedger.weekly properties always return a TokenUsage object (never None), since _PeriodStats.to_token_usage() at src/kimi_cli/token_ledger.py:33-39 always constructs a valid TokenUsage. In KimiSoul.status (src/kimi_cli/soul/kimisoul.py:333-334), these are assigned directly to daily_token_usage and weekly_token_usage. The UI check if status.daily_token_usage is not None is therefore always True, causing two extra toolbar rows displaying all-zero values (today 0 in:0 out:0 / week 0 in:0 out:0) even before any tokens have been used. This is inconsistent with the total_token_usage handling at line 1684 which correctly checks is not None and .total > 0.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

陆逊 added 2 commits March 18, 2026 11:40
- Add robust type coercion in from_dict() to handle strings, floats, invalid values
- Fix concurrent session data loss by reloading from disk before record()
- Extract _read_from_disk() helper for code reuse
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3498df472d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

weekly = None

# Safely extract daily stats with type checking
daily_data = data.get("daily")

Choose a reason for hiding this comment

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

P2 Badge Validate ledger root type before reading daily stats

_read_from_disk assumes json.loads returned a mapping and immediately calls data.get("daily"); if token-stats.json is valid JSON with a non-object root (for example [] after manual edits), this raises AttributeError in TokenLedger.__init__ and can block startup. Fresh evidence: the new type checks only validate daily/weekly after the unguarded data.get(...) call.

Useful? React with 👍 / 👎.

if isinstance(v, int):
return v
if isinstance(v, float):
return int(v)

Choose a reason for hiding this comment

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

P2 Badge Handle non-finite numbers when coercing token counters

The float coercion path uses int(v) directly, so parseable values like 1e309 (decoded as inf) or NaN can raise OverflowError/ValueError while loading stats, and those exceptions are not suppressed by _read_from_disk; this can still crash ledger loading from a malformed-but-parseable file. Fresh evidence: coercion was added, but the float branch still lacks non-finite guards.

Useful? React with 👍 / 👎.

- Validate JSON root type is dict before accessing (handles [] or primitive roots)
- Guard against non-finite numbers (inf, nan) in _to_int() coercion
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 453210016a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +104 to +113
def daily(self) -> TokenUsage:
"""Total token usage for today (all sessions)."""
self._maybe_reset_boundaries()
return self._daily.to_token_usage()

@property
def weekly(self) -> TokenUsage:
"""Total token usage for the current ISO week (all sessions)."""
self._maybe_reset_boundaries()
return self._weekly.to_token_usage()

Choose a reason for hiding this comment

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

P2 Badge Reload persisted counters when serving daily/weekly totals

These accessors are documented and surfaced as "all sessions" totals, but they only run _maybe_reset_boundaries() and then return this process's cached _daily/_weekly values. If another kimi process records usage after this instance starts, this instance keeps showing stale day/week numbers until it records locally or restarts, so the new toolbar can report incorrect cross-session usage. Reload current-period values from disk before returning daily/weekly (or invalidate cache on external changes).

Useful? React with 👍 / 👎.

陆逊 added 2 commits March 18, 2026 12:30
Ensure cross-session usage is accurate by reloading persisted counters
before returning daily/weekly totals. This fixes stale data when multiple
kimi processes are running concurrently.
Resolve conflicts:
- soul/__init__.py: keep both TokenUsage and MCPStatusSnapshot imports
- soul/kimisoul.py: include both token usage and mcp_status in StatusSnapshot
- ui/shell/prompt.py: keep both _session_start_time and new running prompt fields
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f4866ba3e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +333 to +334
daily_token_usage=self._ledger.daily,
weekly_token_usage=self._ledger.weekly,

Choose a reason for hiding this comment

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

P2 Badge Avoid disk I/O in hot status path

This status snapshot now pulls daily_token_usage and weekly_token_usage directly from TokenLedger on every status access, and both properties do synchronous JSON file reads/parses. In the shell UI, status_provider is called during toolbar redraws (e.g., while typing), so this introduces repeated blocking filesystem I/O on the event loop and can noticeably degrade prompt responsiveness, especially on slow or network-backed home directories. Consider caching ledger totals in memory and refreshing them only after record() or on a throttled interval.

Useful? React with 👍 / 👎.

Add _MIN_RELOAD_INTERVAL (1.0s) to prevent blocking filesystem I/O
on every status access. Properties (daily/weekly) use throttled reload,
while record() forces immediate reload for correctness.

Addresses Codex review: 'Avoid disk I/O in hot status path'
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 479e104659

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +173 to +174
raw_data = json.loads(self._file.read_text())
except (json.JSONDecodeError, OSError):

Choose a reason for hiding this comment

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

P2 Badge Handle non-UTF8 token stats files during load

_read_from_disk() is intended to be best-effort, but Path.read_text() can raise UnicodeDecodeError when token-stats.json contains non-UTF8 bytes (for example after file corruption or manual edits). Because that exception is not caught here, TokenLedger initialization can fail and block CLI startup instead of falling back to empty stats. Please treat decode errors the same way as JSON parse errors so damaged stats files do not crash the agent.

Useful? React with 👍 / 👎.

Add UnicodeDecodeError to exception handling in _read_from_disk()
and _maybe_reset_boundaries(). Treats encoding errors same as
parse errors - fall back to empty stats instead of crashing.

Addresses Codex review: 'Handle non-UTF8 token stats files'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant