mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
fix(edit-history): a remote lookup takes the conversation written last, not the first id's
Review round 2: with the stored conversation tried before the launch id, a respawn that resumed the launch conversation after a /clear was missed once claude exited again: the cleared-to conversation still had a file and won, so the edits made after the resume were never read. No fixed order of the ids is right both after a /clear and after such a resume. Without a live conversation the lookup now takes the most recently written transcript of all known ids (stored, claudeSessionId, id); the live one still goes first. Also from the review: - the remote no-transcript note and the docs name login profiles too (.profile, .bash_profile): sshd's `bash -c` reads neither, like .zshrc; - a wiring guard pins that server.ts looks a remote transcript up with remoteTranscriptIds and leaves the local lookup on the hook path; - the browser tests undo the injected remote session in a finally. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
0a6a877415
commit
5bcec26066
9 files changed
+80
-46
No files matched your search
+19
-12
@@ -478,9 +478,9 @@ export function isRemoteClaudeTranscriptPath(path: string): boolean {
|
||||
* Shell lines that set `cfg` to claude's config dir on the remote host: an absolute
|
||||
* `CLAUDE_CONFIG_DIR` from the ssh command's environment, else `$HOME/.claude` — claude's own
|
||||
* rule. ⚠️ The remote claude runs under `$SHELL -i -l -c` (`remoteLoginShellCommand`), this
|
||||
* script under a plain non-interactive `sh`: a `CLAUDE_CONFIG_DIR` set only in an interactive
|
||||
* rc file (`.zshrc`) is not seen here; one from `.zshenv`, sshd `SetEnv` or the system
|
||||
* environment is.
|
||||
* script under a plain non-interactive `sh` from sshd's `$SHELL -c`: a `CLAUDE_CONFIG_DIR` set
|
||||
* only in a login or interactive profile (`.profile`, `.bash_profile`, `.zshrc`) is not seen
|
||||
* here; one from `.zshenv`, sshd `SetEnv` or the system environment is.
|
||||
*/
|
||||
function claudeConfigDirLines(): string[] {
|
||||
return ['cfg=$HOME/.claude', 'case ${CLAUDE_CONFIG_DIR:-} in /*) cfg=$CLAUDE_CONFIG_DIR ;; esac'];
|
||||
@@ -519,10 +519,13 @@ function liveClaudeIdLines(pane: RemoteClaudePane | undefined): string[] {
|
||||
/**
|
||||
* Script that finds a remote claude session's main transcript and prints `\0<path>\0`;
|
||||
* exit {@link TRANSCRIPT_NOT_FOUND_EXIT} when there is none. First the conversation the
|
||||
* claude in `pane` writes now ({@link liveClaudeIdLines}), then each of `ids`; per id the
|
||||
* newest `<config dir>/projects/*\/<id>.jsonl` ({@link claudeConfigDirLines}). `$HOME` is
|
||||
* expanded by the REMOTE shell: the transcript lives in the remote user's home, which the
|
||||
* server cannot know.
|
||||
* claude in `pane` writes now ({@link liveClaudeIdLines}); without one, the most recently
|
||||
* written transcript of ALL `ids`, not the first id that has one: the conversation claude
|
||||
* wrote last is the current one whether a `/clear` moved on from the launch id or a respawn
|
||||
* resumed it again, and no order of the ids gets both right. Each id is looked up as
|
||||
* `<config dir>/projects/*\/<id>.jsonl` ({@link claudeConfigDirLines}). `$HOME` is expanded
|
||||
* by the REMOTE shell: the transcript lives in the remote user's home, which the server
|
||||
* cannot know.
|
||||
*
|
||||
* ⚠️ Runs under an explicit `sh` with `LC_ALL=C`: sshd runs a command in the remote user's
|
||||
* LOGIN shell, and on a zsh host `ls` can be an alias and an unmatched glob aborts the whole
|
||||
@@ -538,14 +541,18 @@ export function buildRemoteClaudeTranscriptLocateCommand(ids: readonly string[],
|
||||
'export LC_ALL',
|
||||
'best=',
|
||||
...claudeConfigDirLines(),
|
||||
...liveLines,
|
||||
`for id in ${liveLines.length ? '$live ' : ''}${safeIds.map((id) => shellescape(id)).join(' ')}; do`,
|
||||
' for f in "$cfg"/projects/*/"$id".jsonl; do',
|
||||
// newest <id>: `best` becomes that id's transcript if it is newer than the current best.
|
||||
'newest() {',
|
||||
' for f in "$cfg"/projects/*/"$1".jsonl; do',
|
||||
' [ -f "$f" ] || continue',
|
||||
' if [ -z "$best" ] || [ "$f" -nt "$best" ]; then best=$f; fi',
|
||||
' done',
|
||||
' [ -n "$best" ] && break',
|
||||
'done',
|
||||
'}',
|
||||
...liveLines,
|
||||
...(liveLines.length ? ['[ -z "$live" ] || newest "$live"'] : []),
|
||||
'if [ -z "$best" ]; then',
|
||||
` for id in ${safeIds.map((id) => shellescape(id)).join(' ')}; do newest "$id"; done`,
|
||||
'fi',
|
||||
`[ -n "$best" ] || exit ${TRANSCRIPT_NOT_FOUND_EXIT}`,
|
||||
'printf \'\\0%s\\0\' "$best"',
|
||||
].join('\n');
|
||||
|
||||
@@ -113,10 +113,11 @@ export interface SyncOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* The conversation ids a REMOTE session's transcript lookup tries, in order, after the one its
|
||||
* claude writes now. No hook tells Codeman of a remote `/clear`, so `claudeSessionId` stays the
|
||||
* launch id; tried first, it would flip the panel back to the cleared conversation as soon as
|
||||
* the remote claude exits. The stored history's conversation is the newest the panel has read.
|
||||
* The conversation ids a REMOTE session's transcript lookup considers when its claude writes
|
||||
* none now; the most recently written of them wins (`buildRemoteClaudeTranscriptLocateCommand`).
|
||||
* The stored history's conversation must be among them: no hook tells Codeman of a remote
|
||||
* `/clear`, so `claudeSessionId` stays the launch id, and without the stored one the panel
|
||||
* would flip back to the cleared conversation as soon as the remote claude exits.
|
||||
*/
|
||||
export function remoteTranscriptIds(session: EditHistorySession, storedTranscriptId: string | null): string[] {
|
||||
return [...new Set([storedTranscriptId, session.claudeSessionId || session.id, session.id])].filter(
|
||||
|
||||
@@ -369,7 +369,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
? t('Showing saved entries only: {reason}', { reason: data.reason || '' })
|
||||
: this.sessions?.get?.(h.sessionId)?.remote
|
||||
? t(
|
||||
'No transcript found on the remote host yet (Claude writes it with the first prompt). A CLAUDE_CONFIG_DIR set only in an interactive shell profile (.zshrc) cannot be found.'
|
||||
'No transcript found on the remote host yet (Claude writes it with the first prompt). A CLAUDE_CONFIG_DIR set only in a shell profile (.profile, .bash_profile, .zshrc) cannot be found.'
|
||||
)
|
||||
: t(
|
||||
'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.'
|
||||
|
||||
@@ -570,8 +570,8 @@
|
||||
'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 无法被找到。',
|
||||
'No transcript found on the remote host yet (Claude writes it with the first prompt). A CLAUDE_CONFIG_DIR set only in an interactive shell profile (.zshrc) cannot be found.':
|
||||
'远程主机上尚未找到记录(Claude 会在第一次提示时写入)。仅在交互式 shell 配置文件(.zshrc)中设置的 CLAUDE_CONFIG_DIR 无法被找到。',
|
||||
'No transcript found on the remote host yet (Claude writes it with the first prompt). A CLAUDE_CONFIG_DIR set only in a shell profile (.profile, .bash_profile, .zshrc) cannot be found.':
|
||||
'远程主机上尚未找到记录(Claude 会在第一次提示时写入)。仅在 shell 配置文件(.profile、.bash_profile、.zshrc)中设置的 CLAUDE_CONFIG_DIR 无法被找到。',
|
||||
'No edits yet.': '暂无编辑。',
|
||||
'No edits by the main agent.': '主智能体没有编辑。',
|
||||
'Load older': '加载更早的编辑',
|
||||
|
||||
Reference in new issue
Block a user