@@ -167,6 +167,37 @@ function firstEmbeddedAgentArg(callIndex = 0) {
167167 return requireMockArg ( runEmbeddedAgentMock , callIndex , "embedded OpenClaw agent argument" ) ;
168168}
169169
170+ async function persistApprovedCliUserTurnFromMock ( args : Record < string , unknown > ) {
171+ const recorder = requireRecord ( args . userTurnTranscriptRecorder , "user turn recorder" ) as {
172+ persistApproved : ( params : {
173+ target : {
174+ transcriptPath : string ;
175+ sessionId : string ;
176+ agentId ?: string ;
177+ sessionKey ?: string ;
178+ cwd : string ;
179+ config ?: OpenClawConfig ;
180+ } ;
181+ } ) => Promise < { message ?: unknown } | undefined > ;
182+ } ;
183+ const persisted = await recorder . persistApproved ( {
184+ target : {
185+ transcriptPath : args . sessionFile as string ,
186+ sessionId : args . sessionId as string ,
187+ agentId : args . agentId as string | undefined ,
188+ sessionKey : args . sessionKey as string | undefined ,
189+ cwd : ( args . cwd as string | undefined ) ?? ( args . workspaceDir as string ) ,
190+ config : args . config as OpenClawConfig | undefined ,
191+ } ,
192+ } ) ;
193+ if ( persisted ?. message ) {
194+ const notify = args . onUserMessagePersisted ;
195+ if ( typeof notify === "function" ) {
196+ await notify ( persisted . message ) ;
197+ }
198+ }
199+ }
200+
170201describe ( "CLI attempt execution" , ( ) => {
171202 let tmpDir : string ;
172203 let storePath : string ;
@@ -807,7 +838,10 @@ describe("CLI attempt execution", () => {
807838 const sessionStore : Record < string , SessionEntry > = { [ sessionKey ] : sessionEntry } ;
808839 await fs . writeFile ( storePath , JSON . stringify ( sessionStore , null , 2 ) , "utf-8" ) ;
809840 const onUserMessagePersisted = vi . fn ( ) ;
810- runCliAgentMock . mockRejectedValueOnce ( new Error ( "cli crashed before reply" ) ) ;
841+ runCliAgentMock . mockImplementationOnce ( async ( args : unknown ) => {
842+ await persistApprovedCliUserTurnFromMock ( requireRecord ( args , "run CLI agent argument" ) ) ;
843+ throw new Error ( "cli crashed before reply" ) ;
844+ } ) ;
811845
812846 await expect (
813847 runAgentAttempt ( {
@@ -846,7 +880,7 @@ describe("CLI attempt execution", () => {
846880
847881 expect ( onUserMessagePersisted ) . toHaveBeenCalledTimes ( 1 ) ;
848882 expect ( firstRunCliAgentArg ( ) . cwd ) . toBe ( taskCwd ) ;
849- const sessionFile = sessionStore [ sessionKey ] ?. sessionFile ?? "" ;
883+ const sessionFile = path . join ( tmpDir , "session.jsonl" ) ;
850884 const entries = await readSessionFileEntries ( sessionFile ) ;
851885 expectRecordFields ( requireRecord ( entries [ 0 ] , "session entry" ) , {
852886 type : "session" ,
@@ -860,6 +894,97 @@ describe("CLI attempt execution", () => {
860894 } ) ;
861895 } ) ;
862896
897+ it ( "does not persist raw CLI prompts when before_agent_run blocks" , async ( ) => {
898+ const sessionKey = "agent:main:subagent:cli-before-run-blocked" ;
899+ const sessionFile = path . join ( tmpDir , "blocked-session.jsonl" ) ;
900+ const sessionEntry : SessionEntry = {
901+ sessionId : "session-cli-before-run-blocked" ,
902+ updatedAt : Date . now ( ) ,
903+ } ;
904+ const onUserMessagePersisted = vi . fn ( ) ;
905+ runCliAgentMock . mockImplementationOnce ( async ( args : unknown ) => {
906+ const cliArgs = requireRecord ( args , "run CLI agent argument" ) ;
907+ await appendSessionTranscriptMessage ( {
908+ transcriptPath : cliArgs . sessionFile as string ,
909+ sessionId : cliArgs . sessionId as string ,
910+ cwd : ( cliArgs . cwd as string | undefined ) ?? ( cliArgs . workspaceDir as string ) ,
911+ config : { } ,
912+ message : {
913+ role : "user" ,
914+ content : [
915+ {
916+ type : "text" ,
917+ text : "Your message could not be sent: The agent cannot read this message. (blocked by policy-plugin)" ,
918+ } ,
919+ ] ,
920+ timestamp : Date . now ( ) ,
921+ __openclaw : {
922+ beforeAgentRunBlocked : {
923+ blockedBy : "policy-plugin" ,
924+ blockedAt : Date . now ( ) ,
925+ } ,
926+ } ,
927+ } ,
928+ } ) ;
929+ return {
930+ payloads : [
931+ {
932+ text : "Your message could not be sent: The agent cannot read this message. (blocked by policy-plugin)" ,
933+ isError : true ,
934+ } ,
935+ ] ,
936+ meta : {
937+ durationMs : 1 ,
938+ finalAssistantVisibleText :
939+ "Your message could not be sent: The agent cannot read this message. (blocked by policy-plugin)" ,
940+ livenessState : "blocked" ,
941+ executionTrace : {
942+ winnerProvider : "claude-cli" ,
943+ winnerModel : "opus" ,
944+ runner : "cli" ,
945+ } ,
946+ } ,
947+ } satisfies EmbeddedAgentRunResult ;
948+ } ) ;
949+
950+ const result = await runAgentAttempt ( {
951+ providerOverride : "claude-cli" ,
952+ originalProvider : "claude-cli" ,
953+ modelOverride : "opus" ,
954+ cfg : { } as OpenClawConfig ,
955+ sessionEntry,
956+ sessionId : sessionEntry . sessionId ,
957+ sessionKey,
958+ sessionAgentId : "main" ,
959+ sessionFile,
960+ workspaceDir : tmpDir ,
961+ body : "runtime wrapper\nsecret prompt" ,
962+ transcriptBody : "secret prompt" ,
963+ isFallbackRetry : false ,
964+ resolvedThinkLevel : "medium" ,
965+ timeoutMs : 1_000 ,
966+ runId : "run-cli-before-run-blocked" ,
967+ opts : { } as Parameters < typeof runAgentAttempt > [ 0 ] [ "opts" ] ,
968+ runContext : { } as Parameters < typeof runAgentAttempt > [ 0 ] [ "runContext" ] ,
969+ spawnedBy : undefined ,
970+ messageChannel : undefined ,
971+ skillsSnapshot : undefined ,
972+ resolvedVerboseLevel : undefined ,
973+ agentDir : tmpDir ,
974+ onAgentEvent : vi . fn ( ) ,
975+ authProfileProvider : "claude-cli" ,
976+ sessionHasHistory : false ,
977+ onUserMessagePersisted,
978+ } ) ;
979+
980+ expect ( result . meta . livenessState ) . toBe ( "blocked" ) ;
981+ expect ( onUserMessagePersisted ) . not . toHaveBeenCalled ( ) ;
982+ const rawTranscript = await fs . readFile ( sessionFile , "utf-8" ) ;
983+ expect ( rawTranscript ) . toContain ( "beforeAgentRunBlocked" ) ;
984+ expect ( rawTranscript ) . toContain ( "The agent cannot read this message" ) ;
985+ expect ( rawTranscript ) . not . toContain ( "secret prompt" ) ;
986+ } ) ;
987+
863988 it ( "persists internal CLI user turns to the active attempt transcript" , async ( ) => {
864989 const sessionKey = "agent:main:subagent:cli-internal-user-before-failure" ;
865990 const visibleSessionFile = path . join ( tmpDir , "visible-session.jsonl" ) ;
@@ -880,7 +1005,10 @@ describe("CLI attempt execution", () => {
8801005 timestamp : Date . now ( ) ,
8811006 } ,
8821007 } ) ;
883- runCliAgentMock . mockRejectedValueOnce ( new Error ( "cli crashed before internal reply" ) ) ;
1008+ runCliAgentMock . mockImplementationOnce ( async ( args : unknown ) => {
1009+ await persistApprovedCliUserTurnFromMock ( requireRecord ( args , "run CLI agent argument" ) ) ;
1010+ throw new Error ( "cli crashed before internal reply" ) ;
1011+ } ) ;
8841012
8851013 await expect (
8861014 runAgentAttempt ( {
0 commit comments