diff --git a/src/tmux-manager.ts b/src/tmux-manager.ts
index ca29478e..ad6eb559 100644
--- a/src/tmux-manager.ts
+++ b/src/tmux-manager.ts
@@ -640,6 +640,10 @@ export function buildCodexCommand(config?: CodexConfig): string {
parts.push('--dangerously-bypass-approvals-and-sandbox');
}
+ if (config?.animations !== undefined) {
+ parts.push('--config', `tui.animations=${config.animations ? 'true' : 'false'}`);
+ }
+
if (config?.model) {
const safeModel = /^[a-zA-Z0-9._\-/]+$/.test(config.model) ? config.model : undefined;
if (safeModel) parts.push('--model', safeModel);
diff --git a/src/types/session.ts b/src/types/session.ts
index 8305759c..9fca7001 100644
--- a/src/types/session.ts
+++ b/src/types/session.ts
@@ -298,6 +298,8 @@ export interface CodexConfig {
resumeSessionId?: string;
/** Bypass approval prompts (passes --dangerously-bypass-approvals-and-sandbox) */
dangerouslyBypassApprovals?: boolean;
+ /** Enable Codex's decorative TUI animations. Disable to reduce remote terminal redraws. */
+ animations?: boolean;
/** Browser rendering strategy for Codex sessions. Hybrid TUI is the only supported mode. */
renderMode?: CodexRenderMode;
}
diff --git a/src/web/public/index.html b/src/web/public/index.html
index 224280db..3021215d 100644
--- a/src/web/public/index.html
+++ b/src/web/public/index.html
@@ -1654,6 +1654,14 @@
diff --git a/src/web/public/session-ui.js b/src/web/public/session-ui.js
index d3cbec61..0031f5fa 100644
--- a/src/web/public/session-ui.js
+++ b/src/web/public/session-ui.js
@@ -1003,6 +1003,7 @@ Object.assign(CodemanApp.prototype, {
...(isRemote ? {} : {
codexConfig: {
dangerouslyBypassApprovals: globalSettings.codexDangerouslyBypassApprovals ?? false,
+ animations: globalSettings.codexAnimationsEnabled ?? false,
renderMode: 'hybrid',
},
...(Object.keys(envOverrides).length > 0 ? { envOverrides } : {}),
diff --git a/src/web/public/settings-ui.js b/src/web/public/settings-ui.js
index 9e0c0d0c..feb28902 100644
--- a/src/web/public/settings-ui.js
+++ b/src/web/public/settings-ui.js
@@ -362,6 +362,8 @@ Object.assign(CodemanApp.prototype, {
// Codex CLI settings
document.getElementById('appSettingsCodexDangerouslyBypassApprovals').checked =
settings.codexDangerouslyBypassApprovals ?? false;
+ document.getElementById('appSettingsCodexAnimations').checked =
+ settings.codexAnimationsEnabled ?? false;
// Claude Permissions settings
document.getElementById('appSettingsAgentTeams').checked = settings.agentTeamsEnabled ?? false;
document.getElementById('appSettingsClaudeModel').value = settings.claudeModel ?? '';
@@ -1467,6 +1469,7 @@ Object.assign(CodemanApp.prototype, {
allowedTools: document.getElementById('appSettingsAllowedTools').value.trim(),
// Codex CLI settings
codexDangerouslyBypassApprovals: document.getElementById('appSettingsCodexDangerouslyBypassApprovals').checked,
+ codexAnimationsEnabled: document.getElementById('appSettingsCodexAnimations').checked,
// Claude Permissions settings
agentTeamsEnabled: document.getElementById('appSettingsAgentTeams').checked,
claudeModel: document.getElementById('appSettingsClaudeModel').value,
diff --git a/src/web/schemas.ts b/src/web/schemas.ts
index a2034184..851c8329 100644
--- a/src/web/schemas.ts
+++ b/src/web/schemas.ts
@@ -182,6 +182,7 @@ const CodexConfigSchema = z
.regex(/^[a-zA-Z0-9_-]+$/)
.optional(),
dangerouslyBypassApprovals: z.boolean().optional(),
+ animations: z.boolean().optional(),
renderMode: z
.enum(['scrollback', 'hybrid'])
.optional()
@@ -756,6 +757,7 @@ export const SettingsUpdateSchema = z
allowedTools: z.string().max(2000).optional(),
// Codex CLI settings
codexDangerouslyBypassApprovals: z.boolean().optional(),
+ codexAnimationsEnabled: z.boolean().optional(),
// Terminal history and retention
terminalScrollbackLines: z
.number()
diff --git a/test/run-mode-ui.test.ts b/test/run-mode-ui.test.ts
index 0c5b25af..194b92f0 100644
--- a/test/run-mode-ui.test.ts
+++ b/test/run-mode-ui.test.ts
@@ -178,11 +178,13 @@ describe('Codex quick start settings', () => {
/
([\s\S]*?)/
);
expect(claudeTab?.[1]).not.toContain('appSettingsCodexDangerouslyBypassApprovals');
+ expect(claudeTab?.[1]).not.toContain('appSettingsCodexAnimations');
const codexTab = html.match(
/
([\s\S]*?)<\/div>\s*/
);
expect(codexTab?.[1]).toContain('appSettingsCodexDangerouslyBypassApprovals');
+ expect(codexTab?.[1]).toContain('appSettingsCodexAnimations');
expect(codexTab?.[1]).not.toContain('appSettingsCodexRenderMode');
});
@@ -222,6 +224,7 @@ describe('Codex quick start settings', () => {
app.terminal = { clear: () => {}, writeln: () => {}, focus: () => {} };
app.loadAppSettingsFromStorage = () => ({
codexDangerouslyBypassApprovals: true,
+ codexAnimationsEnabled: false,
});
app.getCaseSettings = () => ({});
app.buildEnvOverrides = () => ({});
@@ -240,7 +243,7 @@ describe('Codex quick start settings', () => {
mode: 'codex',
// tabs follow the w- naming convention (quick-start would otherwise auto-name codeman-)
sessionName: 'w1-codex-case',
- codexConfig: { dangerouslyBypassApprovals: true, renderMode: 'hybrid' },
+ codexConfig: { dangerouslyBypassApprovals: true, animations: false, renderMode: 'hybrid' },
});
expect(selected).toEqual(['sess-1']);
});
diff --git a/test/tmux-manager.test.ts b/test/tmux-manager.test.ts
index 72545fe4..737e3af1 100644
--- a/test/tmux-manager.test.ts
+++ b/test/tmux-manager.test.ts
@@ -10,6 +10,7 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
TmuxManager,
+ buildCodexCommand,
buildRemoteKillCommand,
buildRemoteLaunchCommand,
formatPaneSnapshot,
@@ -98,6 +99,14 @@ describe('TmuxManager (unit)', () => {
});
});
+ describe('Codex command builder', () => {
+ it('controls decorative TUI animation through Codex config', () => {
+ expect(buildCodexCommand({ animations: false })).toBe('codex --config tui.animations=false');
+ expect(buildCodexCommand({ animations: true })).toBe('codex --config tui.animations=true');
+ expect(buildCodexCommand()).toBe('codex');
+ });
+ });
+
describe('remote launch command builder', () => {
it('wraps codex command overrides in ssh with remote tmux launch', () => {
const command = buildRemoteLaunchCommand({