Skip to content

feat(hooks): use first user prompt as fallback session/agent descriptor#205

Open
hoangsonww wants to merge 2 commits into
masterfrom
feat/first-user-prompt-descriptor
Open

feat(hooks): use first user prompt as fallback session/agent descriptor#205
hoangsonww wants to merge 2 commits into
masterfrom
feat/first-user-prompt-descriptor

Conversation

@hoangsonww

Copy link
Copy Markdown
Owner

Summary

Implements #201: sessions (and their main agent) that never receive a custom-title / ai-title — including sessions imported from existing JSONL transcripts — are now described by their first user prompt instead of showing Session <id8> / Main Agent - Session <id8> forever.

Closes #201

Changes

  • server/lib/transcript-cache.jsTranscriptCache now captures the first real user message per transcript and surfaces it as firstUserMessage alongside the existing extract fields. Tool-result entries, isMeta/caveat lines, local slash-command plumbing (<command-name>, <local-command-stdout>, …), compact summaries, and user-interrupt markers are all skipped; text is whitespace-collapsed and capped at 500 chars. Unlike the last-wins titles, the first value wins and is carried through both full and incremental reads (_merge prefers the cached value). The extraction helper is exported (extractFirstUserText) so live and imported sessions derive the identical descriptor.
  • server/routes/hooks.js — new idempotent applyFirstUserDescriptor helper fills sessions.name and the main agent's name/task only while those fields still hold auto-generated placeholders. It runs strictly after syncSessionName on every transcript-bearing event (live on UserPromptSubmit, backfill on any later event, including for imported sessions) and in the 15 s watchdog. Precedence is fully preserved:
    1. an explicit custom-title always wins;
    2. an ai-title fills a placeholder name — and can still replace a descriptor-filled name later (syncSessionName treats a name equal to the descriptor label as replaceable);
    3. the descriptor only fills what remains.
  • current_tool footgun handled — the shared updateAgent statement writes current_tool verbatim (no COALESCE), so the helper passes the agent's existing current_tool through; an in-flight tool is never wiped mid-turn (covered by a dedicated test that fills the descriptor inside the same transaction as a PreToolUse).
  • scripts/import-history.jsparseSessionFile captures the same descriptor; new imports use it in the name fallback chain below titles (customTitle || aiTitle || firstUserLabel || cwd-derived fallback), set it as the main agent's task, and the re-import backfill pass renames placeholder session/main-agent rows from earlier imports — dead imported sessions never fire hooks, so the startup re-import is their only path to a meaningful label. User-picked names are never clobbered anywhere.
  • Docs — README, ARCHITECTURE.md, server/README.md, docs/HOOKS.md, docs/DATABASE.md updated to describe the new naming precedence.
  • Tests — new server/__tests__/first-user-descriptor.test.js (16 tests): cache extraction/skipping/truncation/incremental first-wins, hook fill + idempotency + title precedence + later-ai-title replacement + user-set-name preservation + current_tool preservation, and import (new rows, backfill, user-picked names kept).

Type of Change

  • New feature (non-breaking change that adds functionality)

How to Test

  1. npm run test:server — 552/552 pass (includes the 16 new tests).
  2. Live: start a session without any title (claude and just type a prompt) — the Kanban/session list shows the prompt text as the session name and Main Agent - <prompt> with the prompt as the agent task, instead of Session <id8>.
  3. Precedence: /rename or wait for the auto ai-title — the title takes over the session name; a name you set in the dashboard is never overwritten.
  4. Import: restart the server with historical JSONL transcripts present — previously placeholder-named imported sessions (<folder> (<slug>), Main Agent - <folder> …) are renamed from their first prompt on the re-import pass.

Checklist

  • I have read the contributing guidelines
  • I have signed the CLA (the 🖋️ CLA Assistant bot will prompt me on my first PR)
  • My code follows the project's coding standards
  • I have added/updated tests that prove my fix or feature works
  • All new and existing tests pass (npm test)
  • Code is formatted (npm run format:check)
  • I have updated documentation where necessary

🤖 Generated with Claude Code

https://claude.ai/code/session_0146oeseRLX17wL3iTqc1trq

Sessions that never receive a custom-title or ai-title (and sessions
imported from existing JSONL transcripts) showed up as "Session <id8>"
and "Main Agent - Session <id8>" forever, making it hard to tell what
each session is doing at a glance (issue #201).

TranscriptCache now captures the first real user message of a transcript
(tool-result, meta/caveat, slash-command plumbing, compact-summary, and
interrupt entries skipped; whitespace-collapsed; capped at 500 chars) and
surfaces it as `firstUserMessage` alongside the existing extract fields.
First value wins across incremental reads, unlike the last-wins titles.

The hook ingestor applies it via an idempotent helper
(applyFirstUserDescriptor) that fills sessions.name and the main agent's
name/task ONLY while those fields still hold auto-generated placeholders.
It runs strictly after syncSessionName on every transcript-bearing event
(live on UserPromptSubmit, backfill on any later event) and in the 15 s
watchdog. Precedence is preserved: custom-title always wins, ai-title
fills placeholders — and can still replace a descriptor-filled name later
— and the descriptor only fills what remains. The main-agent fill passes
the existing current_tool through verbatim (the shared updateAgent
statement writes that column without COALESCE), so an in-flight tool is
never wiped mid-turn.

import-history captures the same descriptor (shared extractFirstUserText)
so newly imported sessions are named from their first prompt when no
title exists, their main agent gets the prompt as its task, and the
re-import backfill pass renames placeholder rows from earlier imports —
dead imported sessions never fire hooks, so this is their only path to a
meaningful label. User-picked names are never clobbered anywhere.

Closes #201

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeseRLX17wL3iTqc1trq
Copilot AI review requested due to automatic review settings July 4, 2026 11:39
@cursor

cursor Bot commented Jul 4, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@github-actions github-actions Bot added bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed question Further information is requested labels Jul 4, 2026

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements a fallback descriptor mechanism using the session's first user prompt (addressing issue #201). When neither a custom nor an AI-generated title is available, the system extracts, normalizes, and truncates the first real user prompt to fill placeholder session names, main agent names, and agent tasks. This behavior is integrated into the live hook ingestor, the 15-second watchdog, and the batch history importer (covering both new imports and re-import backfills). The changes are supported by comprehensive unit tests in a new test file, along with updated documentation across several markdown files. There are no review comments to address, and the implementation is clean and well-tested.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

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

Adds a new “fallback descriptor” for sessions (and their main agent) based on the first real user prompt when no custom-title / ai-title is ever present—improving readability for long-running untitled sessions and imported transcripts while preserving existing title precedence.

Changes:

  • Extend TranscriptCache to extract and persist firstUserMessage (first-wins, whitespace-collapsed, capped) and expose a shared extraction helper for reuse by imports.
  • Apply the descriptor in hook ingestion + watchdog to fill placeholder-only sessions.name and main agent name/task, without clobbering explicit names/titles and without wiping current_tool.
  • Update importer naming/backfill behavior and add a comprehensive server test suite + documentation updates describing the new precedence.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
server/routes/hooks.js Applies the first-user-prompt descriptor as a placeholder-only fallback (after title sync) and in the watchdog; preserves current_tool.
server/lib/transcript-cache.js Extracts firstUserMessage (first-wins) and exports extractFirstUserText for shared reuse.
scripts/import-history.js Uses the same extracted descriptor for imported sessions’ fallback naming and backfills prior placeholder rows.
server/tests/first-user-descriptor.test.js Adds coverage for extraction rules, hook behavior/precedence/idempotency, current_tool preservation, and import behavior.
README.md Documents new naming precedence and descriptor behavior in user-facing feature list.
server/README.md Documents server-side naming sync + descriptor fallback behavior and precedence.
docs/HOOKS.md Documents hook ingestion naming behavior including descriptor fallback and current_tool preservation.
docs/DATABASE.md Updates sessions.name description to include the descriptor fallback.
ARCHITECTURE.md Updates architecture notes to include custom-title > ai-title > first user prompt precedence.

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

Comment thread server/lib/transcript-cache.js
…i, landing

Mirror the session/agent descriptor naming docs (custom-title > ai-title >
first user prompt) to README-VN.md and README-CN.md feature rows, extend the
wiki Sessions Table and Transcript Cache blurbs (with re-keyed zh/vi entries
in i18n-content.js and a cache bump: wiki-v27 -> wiki-v28, i18n ?v=18 -> 19),
and add one line to the landing page's History import card.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeseRLX17wL3iTqc1trq
@cursor

cursor Bot commented Jul 4, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed question Further information is requested

Projects

Development

Successfully merging this pull request may close these issues.

Feature: use the first user prompt as a human-readable session/agent descriptor

2 participants