Skip to content

fix(web): preserve manual session titles - #2102

Merged
RealKai42 merged 6 commits into
mainfrom
web-session-busy-toast
Apr 28, 2026
Merged

fix(web): preserve manual session titles#2102
RealKai42 merged 6 commits into
mainfrom
web-session-busy-toast

Conversation

@wbxl2000

@wbxl2000 wbxl2000 commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

Refs #2101

Description

This PR now keeps the existing busy-session protection in place and narrows the fix to two concrete web session issues:

  • preserve a title finalized by another request or manual rename while generate-title is in flight
  • show toast errors for rename / archive / unarchive / generate-title failures instead of only logging them

The earlier attempt to allow rename / archive / generate-title while a session is busy has been reverted. Review found that the current state file read-modify-write paths can lose concurrent worker updates without a lock, CAS, or field-level merge helper. Safe busy-session metadata edits are tracked separately in #2101.

What changed

  • Re-check fresh session state before writing a generated or fallback title.
  • Return the already-finalized title when title_generated was set while the LLM call or fallback preparation was in progress.
  • Keep get_editable_session busy checks for session rename / archive / generate-title.
  • Keep action failure toasts for rename / archive / unarchive / generate-title.
  • Avoid adding a duplicate delete failure toast, since delete already reports through the existing session error flow.
  • Add a regression test for late AI title generation racing with a manual title.

Tests

  • uv run pytest tests/web/test_sessions_api.py
  • uv run pyright src/kimi_cli/web/api/sessions.py tests/web/test_sessions_api.py
  • npm run typecheck in web
  • git diff --check

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked the related issue, if any.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have run make gen-changelog to update the changelog.
  • I have run make gen-docs to update the user documentation.

Open in Devin Review

Copilot AI review requested due to automatic review settings April 28, 2026 06:55

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

Copy link
Copy Markdown

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: fd6035a352

ℹ️ 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 thread src/kimi_cli/web/api/sessions.py Outdated
from kimi_cli.session_state import load_session_state, save_session_state

session = get_editable_session(session_id, runner)
session = get_session_or_404(session_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restore concurrency guard for PATCH session updates

Switching this endpoint to get_session_or_404 allows renames/archive toggles while the worker is active, but the handler still does a full read-modify-write of state.json (load_session_state then save_session_state) without any lock or CAS. If the running worker writes session state in between (e.g., todo/plan/approval updates via Session.save_state), this PATCH can write an older snapshot back and silently roll back those non-title fields. This race was prevented before by the busy check, so allowing busy edits here introduces state-loss scenarios.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adjusts web session edit behavior so “state-only” mutations (rename/archive/generate-title) are permitted while a session worker is busy, and prevents late AI title generation from overwriting a concurrently finalized manual title.

Changes:

  • Backend: split session existence lookup from “busy” validation; allow PATCH /api/sessions/{id} and POST /api/sessions/{id}/generate-title while busy; preserve busy guards for delete/fork/upload.
  • Backend: re-check fresh session state before writing generated/fallback titles to avoid clobbering a manual finalized title.
  • Frontend/tests: show failure toasts for session metadata actions; add regression test for the late-title overwrite race.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
web/src/hooks/useSessions.ts Adds error toasts for session mutations (delete/rename/archive/unarchive/generate-title).
tests/web/test_sessions_api.py Adds regression coverage ensuring late generate-title does not overwrite a concurrently finalized manual title.
src/kimi_cli/web/api/sessions.py Introduces get_session_or_404, relaxes busy checks for state-only endpoints, and adds fresh-state checks before persisting AI/fallback titles.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 764 to 778
@router.post("/{session_id}/generate-title", summary="Generate session title using AI")
async def generate_session_title(
session_id: UUID,
request: GenerateTitleRequest | None = None,
runner: KimiCLIRunner = Depends(get_runner),
) -> GenerateTitleResponse:
"""Generate a concise session title using AI based on the first conversation turn.

If request body is empty or parameters are missing, the backend will
automatically read the first turn from wire.jsonl.

Safe to invoke while a worker is running: the final write reloads
state from disk to merge concurrent worker changes.
"""
session = get_editable_session(session_id, runner)
session = get_session_or_404(session_id)
session_dir = session.kimi_cli_session.dir

Copilot AI Apr 28, 2026

Copy link

Choose a reason for hiding this comment

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

generate_session_title is now allowed while a session is busy, but the new test only covers the late-write race and doesn’t cover the core behavior change (busy session should still accept generate-title). Consider adding a test that sets up a busy SessionProcess and asserts this endpoint returns 200 instead of the previous "Session is busy" error.

Copilot uses AI. Check for mistakes.
Comment thread web/src/hooks/useSessions.ts Outdated
const message =
err instanceof Error ? err.message : "Failed to delete session";
setError(message);
toast.error(message);

Copilot AI Apr 28, 2026

Copy link

Choose a reason for hiding this comment

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

deleteSession now calls toast.error(message) after setting the global error state. App.tsx already shows a toast whenever sessionsError is set, so a delete failure will produce duplicate toasts. Consider removing the direct toast here (or stop setting the global error for this path) so failures are surfaced exactly once.

Suggested change
toast.error(message);

Copilot uses AI. Check for mistakes.
Comment on lines 608 to 612
from kimi_cli.session_state import load_session_state, save_session_state

session = get_editable_session(session_id, runner)
session = get_session_or_404(session_id)
session_dir = session.kimi_cli_session.dir
state = load_session_state(session_dir)

Copilot AI Apr 28, 2026

Copy link

Choose a reason for hiding this comment

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

This change removes the busy guard for PATCH /api/sessions/{session_id} by switching to get_session_or_404, but there’s no test asserting that updates succeed while a session is busy (and that truly conflicting ops remain blocked). Adding a regression test that simulates a busy SessionProcess and verifies rename/archive still work would help prevent accidental reintroduction of the busy check.

Copilot uses AI. Check for mistakes.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

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

Copy link
Copy Markdown

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: f4a7afecf8

ℹ️ 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 881 to +883
fresh = load_session_state(session_dir)
# Another request or manual rename may have finalized the title while the
# LLM call was in flight. Preserve that newer title instead of clobbering it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Guard generate-title state write against concurrent worker saves

This endpoint is now callable while a worker is busy, but it still does an unlocked read-modify-write (load_session_state then save_session_state). In generate_session_title, if the worker persists session changes (e.g., todos/approval/plan fields via Session.save_state) after fresh is loaded but before this save executes, this handler can write an older snapshot and silently roll back those worker updates. Because the busy check was removed for this route in this commit, this introduces a real state-loss race under concurrent activity.

Useful? React with 👍 / 👎.

# Conflicts:
#	CHANGELOG.md
#	docs/en/release-notes/changelog.md
#	docs/zh/release-notes/changelog.md

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

Copy link
Copy Markdown

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: 50cf65fb16

ℹ️ 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 thread src/kimi_cli/web/api/sessions.py Outdated
from kimi_cli.session_state import load_session_state, save_session_state

session = get_editable_session(session_id, runner)
session = get_session_or_404(session_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reintroduce synchronization for busy PATCH state writes

Allowing PATCH /api/sessions/{session_id} to bypass get_editable_session reintroduces lost-update races because worker-side state writes are not uniformly routed through Session.save_state. Fresh evidence: KimiSoul’s first-turn auto-title path performs its own unlocked load_session_state/save_session_state cycle (src/kimi_cli/soul/kimisoul.py, lines 638–643). If rename/archive PATCH runs while that turn is finishing, whichever write lands last can silently revert the other change (for example, an archive flag flipping back), so this endpoint is not safe while busy without locking/CAS.

Useful? React with 👍 / 👎.

@wbxl2000 wbxl2000 changed the title fix(web): allow state-only session edits while busy fix(web): preserve manual session titles Apr 28, 2026
# Conflicts:
#	CHANGELOG.md
#	docs/en/release-notes/changelog.md
#	docs/zh/release-notes/changelog.md
@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

ℹ️ 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".

@wbxl2000

Copy link
Copy Markdown
Collaborator Author
image

Pressing Enter in the rename input fires handleSaveEdit while the
PATCH is in flight; if the user then clicks the resulting toast or
otherwise shifts focus, onBlur fires the same handler again, sending
a second PATCH and producing a duplicate failure toast. Track an
in-flight ref and short-circuit re-entrant calls.
@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

ℹ️ 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".

@RealKai42
RealKai42 merged commit cc07092 into main Apr 28, 2026
14 checks passed
@RealKai42
RealKai42 deleted the web-session-busy-toast branch April 28, 2026 09:47
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.

3 participants