@@ -984,14 +984,25 @@ export function registerSessionRoutes(
984984 if ( textBlock ) text = textBlock . text ;
985985 }
986986 if ( ! text ) continue ;
987- // Strip XML-like system/command tags that Claude Code injects into transcripts
987+ // Strip XML-like system/command tags and ANSI escapes from transcripts
988988 text = text
989989 . replace ( / < [ ^ > ] + > / g, '' )
990+ . replace ( / \x1b \[ [ 0 - 9 ; ] * [ a - z A - Z ] / g, '' )
990991 . trim ( )
991992 . replace ( / \s + / g, ' ' ) ;
992993 if ( ! text ) continue ;
993- // Skip system-injected messages and slash command artifacts (not real user prompts)
994- if ( / ^ ( C a v e a t : | i n i t \b | c l e a r \b | \/ \w + \w + $ | Y o u a r e a ) / i. test ( text ) ) continue ;
994+ // Skip system-injected messages, slash command artifacts, and expanded skill prompts
995+ if (
996+ / ^ ( C a v e a t : | i n i t \b | c l e a r \b | r e s u m e \b | \/ [ a - z ] [ \w - ] * \b | Y o u a r e a | \[ R e q u e s t | S e t m o d e l t o ) / i. test ( text ) ||
997+ / ^ ( P l e a s e ) ? ( a n a l y z e | r e v i e w ) t h i s c o d e b a s e / i. test ( text ) ||
998+ / ^ ( R e a d | I m p l e m e n t t h e f o l l o w i n g ) .+ , t h e n ( s e a r c h | l i s t | c h e c k ) / i. test ( text ) ||
999+ / ^ \d + v u l n e r a b i l i t / i. test ( text ) ||
1000+ / \b t o o l u _ / . test ( text ) ||
1001+ / ^ [ A - Z a - z 0 - 9 _ - ] { 20 , } \. [ A - Z a - z 0 - 9 _ - ] + / . test ( text ) ||
1002+ / \b ( s k - a n t - | A N T H R O P I C _ A P I _ K E Y | A P I _ K E Y = | S E C R E T | T O K E N = ) / i. test ( text ) ||
1003+ text . length < 8
1004+ )
1005+ continue ;
9951006 return text . length > MAX_PROMPT_LEN ? text . slice ( 0 , MAX_PROMPT_LEN ) + '…' : text ;
9961007 } catch {
9971008 // Malformed line — skip
@@ -1012,6 +1023,25 @@ export function registerSessionRoutes(
10121023 }
10131024 }
10141025
1026+ /** Read the last `buf.length` bytes of a file (for tail-scanning user prompts). */
1027+ async function readFileTail ( path : string , buf : Buffer , fileSize : number ) : Promise < string | null > {
1028+ try {
1029+ const fd = await fs . open ( path , 'r' ) ;
1030+ const offset = Math . max ( 0 , fileSize - buf . length ) ;
1031+ const { bytesRead } = await fd . read ( buf , 0 , buf . length , offset ) ;
1032+ await fd . close ( ) ;
1033+ const text = buf . toString ( 'utf8' , 0 , bytesRead ) ;
1034+ // Skip first partial line when we didn't read from the start
1035+ if ( offset > 0 ) {
1036+ const nl = text . indexOf ( '\n' ) ;
1037+ return nl >= 0 ? text . slice ( nl + 1 ) : null ;
1038+ }
1039+ return text ;
1040+ } catch {
1041+ return null ;
1042+ }
1043+ }
1044+
10151045 app . get ( '/api/history/sessions' , async ( ) => {
10161046 const projectsDir = join ( process . env . HOME || '/tmp' , '.claude' , 'projects' ) ;
10171047 const results : Array < {
@@ -1074,6 +1104,14 @@ export function registerSessionRoutes(
10741104 }
10751105 if ( head ) firstPrompt = extractFirstUserPrompt ( head ) ;
10761106
1107+ // If head scan found no usable prompt (e.g. session started with /init),
1108+ // try reading the tail for a recent user message.
1109+ if ( ! firstPrompt && fileStat . size > 65536 ) {
1110+ const tailBuf = Buffer . alloc ( 32768 ) ;
1111+ const tail = await readFileTail ( filePath , tailBuf , fileStat . size ) ;
1112+ if ( tail ) firstPrompt = extractFirstUserPrompt ( tail ) ;
1113+ }
1114+
10771115 results . push ( {
10781116 sessionId,
10791117 workingDir,
0 commit comments