[codex] add Codex usage indicator#537
Conversation
There was a problem hiding this comment.
Findings
-
[Major] Codex session listing bypasses the runner workspace scope — the new machine RPC returns every transcript under
CODEX_HOME, including title/path, without using the existing workspace-root guard that protects machine directory listing and spawn. On a scoped runner, this lets the web UI enumerate Codex sessions outside the allowed workspace. Evidencecli/src/modules/common/handlers/codexSessions.ts:15.
Suggested fix:const allowed = options?.isPathAllowed const sessions = [] for (const session of result.sessions) { if (!allowed || await allowed(session.path)) { sessions.push(session) } } return { success: true, sessions, nextCursor: result.nextCursor }
-
[Major]
importHistoryis dropped before the runner builds Codex args — the hub now sendsimportHistorytospawn-happy-session, but the machine RPC handler does not destructure or forward it tospawnSession, sobuildCliArgs()never sees it and never adds--hapi-import-history. Resuming a Codex session from the new selector starts the Codex thread but does not import the transcript into HAPI. Evidencehub/src/sync/rpcGateway.ts:158.
Suggested fix:const { directory, sessionId, resumeSessionId, machineId, approvedNewDirectoryCreation, agent, model, effort, modelReasoningEffort, yolo, permissionMode, token, sessionType, worktreeName, importHistory } = params || {} const result = await spawnSession({ directory, sessionId, resumeSessionId, importHistory, // existing fields... })
-
[Minor] Local
--hapi-import-historycan replay the same transcript twice —runCodex()imports the transcript before launching, then the local scanner is started withreplayExistingEvents: session.importHistoryand sends those same existing user/agent messages again. Evidencecli/src/codex/codexLocalLauncher.ts:73.
Suggested fix:const createdScanner = await createCodexSessionScanner({ transcriptPath, replayExistingEvents: false, onSessionId: (sessionId) => { session.onSessionFound(sessionId) }, // existing onEvent... })
Questions
- None.
Summary
Review mode: initial
3 findings: one workspace-scope privacy regression, one broken import-history propagation path, and one local duplicate-history edge case.
Testing
- Not run (automation); static
git diff --checkpassed.
HAPI Bot
| logger.debug('List Codex sessions request'); | ||
|
|
||
| try { | ||
| const result = await listCodexSessions(data ?? {}); |
There was a problem hiding this comment.
[MAJOR] This handler returns every Codex transcript under CODEX_HOME with title/path, but it is registered as a machine RPC and does not receive the runner's workspaceRoot guard. A scoped runner already blocks machine directory listing/spawn outside the root; this endpoint now bypasses that boundary and exposes sessions from unrelated directories.
Suggested fix:
const allowed = options?.isPathAllowed
const sessions = []
for (const session of result.sessions) {
if (!allowed || await allowed(session.path)) {
sessions.push(session)
}
}
return { success: true, sessions, nextCursor: result.nextCursor }| machineId, | ||
| 'spawn-happy-session', | ||
| { type: 'spawn-in-directory', directory, agent, model, modelReasoningEffort, yolo, sessionType, worktreeName, resumeSessionId, effort, permissionMode } | ||
| { type: 'spawn-in-directory', directory, agent, model, modelReasoningEffort, yolo, sessionType, worktreeName, resumeSessionId, importHistory, effort, permissionMode } |
There was a problem hiding this comment.
[MAJOR] importHistory is sent here, but the receiving machine RPC handler currently destructures params without importHistory and does not include it in the spawnSession() options. As a result, runner-spawned Codex resumes never pass --hapi-import-history, so the new selector resumes the Codex thread without importing prior transcript messages into HAPI.
Suggested fix:
const { directory, sessionId, resumeSessionId, machineId, approvedNewDirectoryCreation, agent, model, effort, modelReasoningEffort, yolo, permissionMode, token, sessionType, worktreeName, importHistory } = params || {}
const result = await spawnSession({
directory,
sessionId,
resumeSessionId,
importHistory,
machineId,
approvedNewDirectoryCreation,
agent,
model,
effort,
modelReasoningEffort,
yolo,
permissionMode,
token,
sessionType,
worktreeName
})| } | ||
| const createdScanner = await createCodexSessionScanner({ | ||
| transcriptPath, | ||
| replayExistingEvents: session.importHistory, |
There was a problem hiding this comment.
[MINOR] runCodex() already imports the transcript before launch when --hapi-import-history is set. In local mode this scanner then replays the existing transcript and sends the same user/agent messages again, producing duplicate history in HAPI for hapi codex resume <id> --hapi-import-history.
Suggested fix:
const createdScanner = await createCodexSessionScanner({
transcriptPath,
replayExistingEvents: false,
onSessionId: (sessionId) => {
session.onSessionFound(sessionId)
},
// existing onEvent...
})|
Hi @dsus4wang — thanks for this PR; the design (compact usage ring beside the send button + State today (no judgement, just summarising):
I'm putting together an umbrella for cross-flavor agent budget/quota gauges (#846) that intentionally treats this PR as the natural seed — Codex is the most-baked piece, and I'd like to align Claude (via statusline-JSON To help unblock this, since
That keeps the PR number, your authorship on the original commits, and the existing thread intact. I'd add Are you still planning to iterate on this yourself? If yes, I'll stand down and just track #846 around it. If you're blocked / waiting for review and would welcome the rebase, no need to do anything — I'll proceed if I don't hear back from you within ~7 days. Either way, your name stays on the original work. Disclosure: this comment was drafted by Claude Sonnet 4.6 (model |
|
Hi @dsus4wang - I rebased this PR onto current The rebase also extended the indicator to handle Codex Pro accounts that bill from credits when the subscription windows are exhausted (codex emits Happy for you to take #847 over as primary if you want to keep ownership, or to land it as-is with attribution preserved. Either is fine - I just didn't want this stuck for another 179 commits. |
Summary
Adds live Codex usage state and a composer usage ring for Codex sessions.
Changes
token_countevents from app-server notifications and transcript tailing.session.metadata.codexUsageand sends metadata patches over SSE.Validation
bun run test:cli -- src/codex/codexRemoteLauncher.test.ts src/codex/utils/codexUsage.test.tscd shared && bun test src/codexUsageSchema.test.tscd web && bun run test -- src/components/AssistantChat/codexUsageDisplay.test.tsbun typecheck