From ea5f6615c81482a61ec3482d9574fe5d7b24e0ec Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Fri, 6 Nov 2026 07:45:30 +0100 Subject: [PATCH] fix: name adoption in two refusals an adopted session gets (release review) The Edit history panel told an adopted session it needed to be a Claude session outside Docker, and a stop wait blamed the missing Claude Code hooks on the mode. Both refusals are right, only the words misled. Each now says Codeman did not start the session, with a zh-CN entry for the panel. Co-Authored-By: Claude Opus 5.5 --- src/web/public/edit-history-ui.js | 8 +++++++- src/web/public/i18n.js | 2 ++ src/web/session-wait-registry.ts | 6 ++++-- test/session-wait-registry.test.ts | 11 +++++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/web/public/edit-history-ui.js b/src/web/public/edit-history-ui.js index c871031a..a0954d86 100644 --- a/src/web/public/edit-history-ui.js +++ b/src/web/public/edit-history-ui.js @@ -358,7 +358,13 @@ Object.assign(CodemanApp.prototype, { const data = h.data; if (data.state === 'unsupported') { body.append( - el('div', 'edit-history-empty', t('Edit history is only available for Claude sessions outside Docker.')) + el( + 'div', + 'edit-history-empty', + data.reason === 'adopted' + ? t('Edit history is not available for a tmux session opened outside Codeman.') + : t('Edit history is only available for Claude sessions outside Docker.') + ) ); return; } diff --git a/src/web/public/i18n.js b/src/web/public/i18n.js index 4bff1536..b47da9f3 100644 --- a/src/web/public/i18n.js +++ b/src/web/public/i18n.js @@ -567,6 +567,8 @@ 'Could not read the edit history.': '无法读取编辑历史。', 'Edit history is only available for Claude sessions outside Docker.': '编辑历史仅适用于 Docker 之外的 Claude 会话。', + 'Edit history is not available for a tmux session opened outside Codeman.': + '在 Codeman 之外打开的 tmux 会话无法使用编辑历史。', 'Showing saved entries only: {reason}': '仅显示已保存的条目:{reason}', 'No transcript found for this session yet. It is picked up with the next prompt; until then a CLAUDE_CONFIG_DIR set outside Codeman cannot be found.': '尚未找到此会话的记录。它会在下一次提示时被识别;在此之前,在 Codeman 之外设置的 CLAUDE_CONFIG_DIR 无法被找到。', diff --git a/src/web/session-wait-registry.ts b/src/web/session-wait-registry.ts index f0ab0149..c9a3f548 100644 --- a/src/web/session-wait-registry.ts +++ b/src/web/session-wait-registry.ts @@ -359,8 +359,10 @@ export function resolveWaitSignals( // A dsh session is the one case where the mode is capable and THIS session // is not, so saying "never fire for deepseek sessions" would send the // caller looking for a bug that is really a setting they chose. - error: - options.mode === 'deepseek' + error: options.adopted + ? `Signal(s) ${rejected.join(', ')} never fire for an adopted session: Codeman did not start it, so it ` + + `installed no hooks there. Use ${unsupported.has('blocked') ? 'idle or exit' : 'idle, blocked or exit'}.` + : options.mode === 'deepseek' ? options.deepSeekBridgeUnreachable ? `Signal(s) ${rejected.join(', ')} never fire for this deepseek session: it runs in a container or on ` + `a remote host, where the local status bridge cannot reach the harness. Use idle or exit.` diff --git a/test/session-wait-registry.test.ts b/test/session-wait-registry.test.ts index 4c8ea74a..4fcabf8f 100644 --- a/test/session-wait-registry.test.ts +++ b/test/session-wait-registry.test.ts @@ -170,6 +170,17 @@ describe('resolveWaitSignals', () => { expect(resolveWaitSignals('blocked', { mode: 'gemini' as SessionMode }).error).toBeTruthy(); }); + it('names adoption, not the mode, when an adopted session refuses `stop`', () => { + // An adopted session can be `mode: claude` with no hooks installed, and "no Claude + // Code hooks for claude sessions" sent the caller looking for a bug in the wrong place. + const refusal = resolveWaitSignals('stop', { ...claude, adopted: true }); + + expect(refusal.until).toEqual([]); + expect(refusal.error).toContain('adopted session'); + expect(refusal.error).toContain('idle or exit'); + expect(refusal.error).not.toContain('claude sessions'); + }); + it('accepts `blocked` on codex, whose dialogs are read off the screen, but still not `stop`', () => { // capabilities.workDetect.blockedLine gives codex a source for `blocked`; nothing on a // screen says a turn ended, so `stop` keeps its 400.