From 156062170d5683a96127c632cc45f934e0bedb57 Mon Sep 17 00:00:00 2001 From: Alex Newman Date: Wed, 6 May 2026 14:24:40 -0700 Subject: [PATCH] fix: migrate codex context off agents injection --- .../integrations/CodexCliInstaller.ts | 49 +++++++++++++++++++ src/services/transcripts/config.ts | 7 +-- tests/install-non-tty.test.ts | 22 +++++++++ transcript-watch.example.json | 16 +++--- 4 files changed, 78 insertions(+), 16 deletions(-) diff --git a/src/services/integrations/CodexCliInstaller.ts b/src/services/integrations/CodexCliInstaller.ts index 3f91bce9..bfee3634 100644 --- a/src/services/integrations/CodexCliInstaller.ts +++ b/src/services/integrations/CodexCliInstaller.ts @@ -4,9 +4,11 @@ import { execFileSync, spawnSync } from 'child_process'; import { existsSync, readFileSync, writeFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { logger } from '../../utils/logger.js'; +import { paths } from '../../shared/paths.js'; const CODEX_DIR = path.join(homedir(), '.codex'); const CODEX_AGENTS_MD_PATH = path.join(CODEX_DIR, 'AGENTS.md'); +const CODEX_TRANSCRIPT_WATCH_CONFIG_PATH = paths.transcriptsConfig(); const MARKETPLACE_NAME = 'claude-mem-local'; const MIN_CODEX_MARKETPLACE_VERSION = '0.128.0'; const REQUIRED_MARKETPLACE_FILES = [ @@ -172,6 +174,46 @@ function readAndStripContextTags(startTag: string, endTag: string): void { const cleanupLegacyCodexAgentsMdContext = removeCodexAgentsMdContext; +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +function isCodexTranscriptWatch(watch: Record): boolean { + if (watch.name === 'codex' || watch.schema === 'codex') return true; + return typeof watch.path === 'string' + && watch.path.includes('.codex') + && watch.path.includes('sessions'); +} + +function disableCodexTranscriptAgentsContext(): boolean { + if (!existsSync(CODEX_TRANSCRIPT_WATCH_CONFIG_PATH)) return true; + + try { + const parsed = JSON.parse(readFileSync(CODEX_TRANSCRIPT_WATCH_CONFIG_PATH, 'utf-8')) as unknown; + if (!isRecord(parsed) || !Array.isArray(parsed.watches)) return true; + + let changed = false; + for (const watch of parsed.watches) { + if (!isRecord(watch) || !isCodexTranscriptWatch(watch)) continue; + if (!isRecord(watch.context) || watch.context.mode !== 'agents') continue; + delete watch.context; + changed = true; + } + + if (changed) { + writeFileSync(CODEX_TRANSCRIPT_WATCH_CONFIG_PATH, `${JSON.stringify(parsed, null, 2)}\n`); + console.log(` Disabled legacy Codex transcript AGENTS.md context in ${CODEX_TRANSCRIPT_WATCH_CONFIG_PATH}`); + } + return true; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.warn('WORKER', 'Failed to disable Codex transcript AGENTS.md context', { error: message }); + return false; + } +} + +const cleanupLegacyCodexTranscriptAgentsContext = disableCodexTranscriptAgentsContext; + export async function installCodexCli(marketplaceRootOverride?: string): Promise { console.log('\nInstalling Claude-Mem for Codex CLI (native hooks)...\n'); @@ -190,6 +232,9 @@ export async function installCodexCli(marketplaceRootOverride?: string): Promise if (!cleanupLegacyCodexAgentsMdContext()) { console.warn(` Native Codex hooks registered, but failed to remove legacy AGENTS.md context from ${CODEX_AGENTS_MD_PATH}.`); } + if (!cleanupLegacyCodexTranscriptAgentsContext()) { + console.warn(` Native Codex hooks registered, but failed to disable legacy transcript AGENTS.md context in ${CODEX_TRANSCRIPT_WATCH_CONFIG_PATH}.`); + } console.log(` Installation complete! @@ -235,6 +280,10 @@ export function uninstallCodexCli(): number { console.error(`\nFailed to remove legacy AGENTS.md context from ${CODEX_AGENTS_MD_PATH}.`); failed = true; } + if (!cleanupLegacyCodexTranscriptAgentsContext()) { + console.error(`\nFailed to disable legacy transcript AGENTS.md context in ${CODEX_TRANSCRIPT_WATCH_CONFIG_PATH}.`); + failed = true; + } } catch (error) { const message = error instanceof Error ? error.message : String(error); console.error(`\nLegacy AGENTS.md cleanup failed: ${message}`); diff --git a/src/services/transcripts/config.ts b/src/services/transcripts/config.ts index 7e3cff3f..5e239bbe 100644 --- a/src/services/transcripts/config.ts +++ b/src/services/transcripts/config.ts @@ -79,7 +79,6 @@ const CODEX_SAMPLE_SCHEMA: TranscriptSchema = { }, { name: 'session-end', - // TODO(#2249): delete watcher when Codex hook lifecycle migration ships match: { path: 'payload.type', in: ['turn_aborted', 'turn_completed', 'task_complete'] }, action: 'session_end' } @@ -96,11 +95,7 @@ export const SAMPLE_CONFIG: TranscriptWatchConfig = { name: 'codex', path: '~/.codex/sessions/**/*.jsonl', schema: 'codex', - startAtEnd: true, - context: { - mode: 'agents', - updateOn: ['session_start', 'session_end'] - } + startAtEnd: true } ], stateFile: DEFAULT_STATE_PATH diff --git a/tests/install-non-tty.test.ts b/tests/install-non-tty.test.ts index c755353c..a13eb639 100644 --- a/tests/install-non-tty.test.ts +++ b/tests/install-non-tty.test.ts @@ -27,6 +27,15 @@ const syncMarketplaceSourcePath = join( 'sync-marketplace.cjs', ); const syncMarketplaceSource = readFileSync(syncMarketplaceSourcePath, 'utf-8'); +const transcriptConfigSourcePath = join( + __dirname, + '..', + 'src', + 'services', + 'transcripts', + 'config.ts', +); +const transcriptConfigSource = readFileSync(transcriptConfigSourcePath, 'utf-8'); describe('Install Non-TTY Support', () => { describe('isInteractive flag', () => { @@ -147,7 +156,9 @@ describe('Install Non-TTY Support', () => { it('reports legacy Codex AGENTS cleanup failures to callers', () => { expect(codexInstallerSource).toContain('function removeCodexAgentsMdContext(): boolean'); + expect(codexInstallerSource).toContain('function disableCodexTranscriptAgentsContext(): boolean'); expect(codexInstallerSource).toContain('if (!cleanupLegacyCodexAgentsMdContext())'); + expect(codexInstallerSource).toContain('if (!cleanupLegacyCodexTranscriptAgentsContext())'); }); it('does not fail Codex install after marketplace registration when only AGENTS cleanup fails', () => { @@ -162,6 +173,17 @@ describe('Install Non-TTY Support', () => { expect(cleanupFailureRegion).toContain('console.warn'); expect(cleanupFailureRegion).not.toContain('return 1'); }); + + it('does not seed new Codex transcript watcher configs with AGENTS context injection', () => { + expect(transcriptConfigSource).toContain("name: 'codex'"); + const codexWatchRegion = transcriptConfigSource.slice( + transcriptConfigSource.indexOf("name: 'codex'"), + transcriptConfigSource.indexOf('stateFile: DEFAULT_STATE_PATH'), + ); + expect(codexWatchRegion).toContain("path: '~/.codex/sessions/**/*.jsonl'"); + expect(codexWatchRegion).not.toContain("mode: 'agents'"); + expect(codexWatchRegion).not.toContain('updateOn'); + }); }); describe('TaskDescriptor interface', () => { diff --git a/transcript-watch.example.json b/transcript-watch.example.json index 657d24d8..56529d29 100644 --- a/transcript-watch.example.json +++ b/transcript-watch.example.json @@ -3,7 +3,7 @@ "schemas": { "codex": { "name": "codex", - "version": "0.2", + "version": "0.3", "description": "Schema for Codex session JSONL files under ~/.codex/sessions.", "events": [ { @@ -41,7 +41,7 @@ }, { "name": "tool-use", - "match": { "path": "payload.type", "in": ["function_call", "custom_tool_call", "web_search_call"] }, + "match": { "path": "payload.type", "in": ["function_call", "custom_tool_call", "web_search_call", "exec_command"] }, "action": "tool_use", "fields": { "toolId": "payload.call_id", @@ -55,6 +55,7 @@ "coalesce": [ "payload.arguments", "payload.input", + "payload.command", "payload.action" ] } @@ -62,7 +63,7 @@ }, { "name": "tool-result", - "match": { "path": "payload.type", "in": ["function_call_output", "custom_tool_call_output"] }, + "match": { "path": "payload.type", "in": ["function_call_output", "custom_tool_call_output", "exec_command_output"] }, "action": "tool_result", "fields": { "toolId": "payload.call_id", @@ -71,7 +72,7 @@ }, { "name": "session-end", - "match": { "path": "payload.type", "equals": "turn_aborted" }, + "match": { "path": "payload.type", "in": ["turn_aborted", "turn_completed", "task_complete"] }, "action": "session_end" } ] @@ -82,12 +83,7 @@ "name": "codex", "path": "~/.codex/sessions/**/*.jsonl", "schema": "codex", - "startAtEnd": true, - "context": { - "mode": "agents", - "path": "~/.codex/AGENTS.md", - "updateOn": ["session_start", "session_end"] - } + "startAtEnd": true } ], "stateFile": "~/.claude-mem/transcript-watch-state.json"