fix: release codex lease on readonly prep failure

This commit is contained in:
ejclaw
2026-06-06 18:47:04 +09:00
parent ac0cd3e41d
commit 29ca23a558
3 changed files with 75 additions and 15 deletions

1
.gitignore vendored
View File

@@ -39,6 +39,7 @@ tmp/
backups/ backups/
dist-backups/ dist-backups/
.deploy-backups/ .deploy-backups/
recovery/
cache/ cache/
runners/*/node_modules/ runners/*/node_modules/
runners/codex-runner-backups/ runners/codex-runner-backups/

View File

@@ -474,6 +474,55 @@ describe('agent-runner timeout behavior', () => {
expect(spawnEnv?.CODEX_HOME).toBe('/tmp/host-reviewer-session/.codex'); expect(spawnEnv?.CODEX_HOME).toBe('/tmp/host-reviewer-session/.codex');
}); });
it('releases the initial Codex auth lease when read-only session preparation fails', async () => {
vi.useRealTimers();
fakeProc = createFakeProcess();
const releaseLease = vi.fn();
const prepareGroupEnvironmentSpy = mockCodexLeaseEnvironment(releaseLease);
const readonlyError = new Error('Codex rotation pool unavailable');
const prepareReadonlySessionEnvironmentSpy = vi
.spyOn(agentRunnerEnvironment, 'prepareReadonlySessionEnvironment')
.mockImplementationOnce(() => {
throw readonlyError;
});
const codexGroup: RegisteredGroup = {
...testGroup,
agentType: 'codex',
};
try {
await expect(
runAgentProcess(
codexGroup,
{
...testInput,
roomRoleContext: {
serviceId: 'codex-review',
role: 'reviewer',
ownerServiceId: 'codex-main',
reviewerServiceId: 'codex-review',
failoverOwner: false,
},
},
() => {},
async () => {},
{
EJCLAW_UNSAFE_HOST_PAIRED_MODE: '1',
CLAUDE_CONFIG_DIR: '/tmp/host-reviewer-session',
EJCLAW_WORK_DIR: '/tmp/paired/task-1/reviewer',
},
),
).rejects.toThrow(readonlyError);
expect(prepareReadonlySessionEnvironmentSpy).toHaveBeenCalledTimes(1);
expect(releaseLease).toHaveBeenCalledTimes(1);
} finally {
prepareReadonlySessionEnvironmentSpy.mockRestore();
prepareGroupEnvironmentSpy.mockRestore();
}
});
it('serializes roomRoleContext into the runner stdin payload', async () => { it('serializes roomRoleContext into the runner stdin payload', async () => {
vi.useRealTimers(); vi.useRealTimers();
fakeProc = createFakeProcess(); fakeProc = createFakeProcess();

View File

@@ -157,7 +157,11 @@ export async function runAgentProcess(
(input.roomRoleContext?.role === 'reviewer' || (input.roomRoleContext?.role === 'reviewer' ||
input.roomRoleContext?.role === 'arbiter') input.roomRoleContext?.role === 'arbiter')
) { ) {
const readonlySession = prepareReadonlySessionEnvironment({ const isCodexGroup = (group.agentType || 'claude-code') === 'codex';
const previousCodexSessionAuth = codexSessionAuth;
let readonlySession: ReturnType<typeof prepareReadonlySessionEnvironment>;
try {
readonlySession = prepareReadonlySessionEnvironment({
sessionDir: envOverrides.CLAUDE_CONFIG_DIR, sessionDir: envOverrides.CLAUDE_CONFIG_DIR,
chatJid: input.chatJid, chatJid: input.chatJid,
isMain: input.isMain, isMain: input.isMain,
@@ -170,8 +174,14 @@ export async function runAgentProcess(
workDir: envOverrides[EJCLAW_ENV.workDir] || env[EJCLAW_ENV.workDir], workDir: envOverrides[EJCLAW_ENV.workDir] || env[EJCLAW_ENV.workDir],
skillOverrides, skillOverrides,
}); });
if ((group.agentType || 'claude-code') === 'codex') { } catch (err) {
releaseCodexAuthSession(codexSessionAuth); if (isCodexGroup) {
releaseCodexAuthSession(previousCodexSessionAuth);
}
throw err;
}
if (isCodexGroup) {
releaseCodexAuthSession(previousCodexSessionAuth);
codexSessionAuth = readonlySession.codexSessionAuth; codexSessionAuth = readonlySession.codexSessionAuth;
env.CODEX_HOME = path.join(envOverrides.CLAUDE_CONFIG_DIR, '.codex'); env.CODEX_HOME = path.join(envOverrides.CLAUDE_CONFIG_DIR, '.codex');
if (readonlySession.codexHomeDir) { if (readonlySession.codexHomeDir) {