CLI/플러그인 릴리스 감지 및 자동 업데이트 추가 - #4
Conversation
- packages/cli/src/core/update.server.ts 신규: GitHub 최신 릴리스 조회, 버전 비교(단순 문자열 비교가 아닌 숫자 필드별 비교), 24시간 캐시된 백그라운드 체크(3초 타임아웃, 실패해도 절대 던지지 않음), 플랫폼별 바이너리 자체 교체 로직(Windows는 실행 중인 exe를 덮어쓰거나 지울 수 없어 .old.exe로 먼저 rename한 뒤 새 바이너리를 원래 이름으로 rename하는 방식 사용) - sessionforge check-update / sessionforge update 명령 추가 — 감지는 자동(다른 모든 명령 끝에 한 줄 알림)이지만 실제 교체는 항상 명시적 명령 으로만 — 사용자 동의 없이 실행 중인 바이너리를 몰래 바꾸지 않음 - 실제로 검증함: 별도로 복사한 사본에서 v0.1.0 → 진짜 v0.2.0 GitHub Release로 실제 다운로드 및 자체 교체 성공, 교체된 바이너리 정상 동작 확인 - scripts/package-plugin.mjs가 패키징된 플러그인 번들에 .sessionforge-version 마커 파일을 기록하도록 함 (release.yml이 SESSIONFORGE_VERSION 전달) - paseo-wire.server.ts에 getInstalledPluginVersion 추가, paseo-status가 설치된 플러그인 버전과 실행 중인 CLI 버전의 드리프트를 보여주도록 확장 (dev-main 빌드에서는 비교 자체가 의미 없으므로 드리프트 안내를 생략함) — 실제 바이너리로 드리프트 감지와 "unknown"(마커 없는 구버전 설치) 케이스 모두 실제로 확인함 - 테스트 31개 추가(update.server 19개, paseo-wire 2개), 전체 121개 통과 - README.md, README.ko.md, docs/MANUAL.md(새 "Staying up to date" 섹션), docs/REFERENCE.md, packages/cli/README.md 문서 갱신
- downloadCliBinary 테스트: process.platform을 "linux"로 흉내내도 chmodSync의 실행 비트는 실제 호스트가 진짜 Linux/macOS일 때만 의미가 있음 — Windows NTFS에는 그런 개념이 없어 실행 비트 검증만 실제 POSIX일 때로 분리 (activity.server.test.ts의 실제 /proc 테스트와 동일한 원칙) - checkForUpdateCached 테스트: process.env.HOME만 오버라이드하고 USERPROFILE은 빼먹어서, 실제 윈도우에서는 os.homedir()이 진짜 사용자 홈 디렉터리를 가리켜 캐시 파일이 샌드박스 밖으로 새어나가 테스트 간 오염이 발생했음 — 이미 이번 세션에서 trash.server.test.ts에 적용했던 것과 완전히 같은 종류의 버그를 새 테스트 파일에 또 반복한 것이라 동일하게 HOME과 USERPROFILE을 함께 오버라이드하도록 수정 - 로컬에서 typecheck 통과, 테스트 122개 전부 통과 확인
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6cf1961618
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| const isWindows = process.platform === "win32"; | ||
| const newBinaryPath = join(tmpdir(), `sessionforge-update-${latest}${isWindows ? ".exe" : ""}`); |
There was a problem hiding this comment.
Create update files without following temp symlinks
On a shared temporary directory, another local user can pre-create the predictable sessionforge-update-<latest> pathname as a symlink to a file they control before the victim runs update. downloadCliBinary follows that symlink when writing, and selfReplaceBinary then renames the symlink into the victim's executable path; the attacker can subsequently replace its target so the victim executes attacker-controlled code. Use a private, uniquely created staging file (or an exclusive file in the executable's directory) rather than this predictable tmpdir() name.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| const isWindows = process.platform === "win32"; | ||
| const newBinaryPath = join(tmpdir(), `sessionforge-update-${latest}${isWindows ? ".exe" : ""}`); |
There was a problem hiding this comment.
Stage the replacement on the binary's filesystem
The updater downloads into tmpdir(), but selfReplaceBinary uses rename to install it. When /tmp is a separate filesystem from the installed binary (for example, the documented ~/.local/bin/sessionforge on Linux systems with a tmpfs /tmp), that rename fails with EXDEV, so every update leaves the old binary installed. Stage the download in the executable's directory, or copy it there before the final same-filesystem rename.
Useful? React with 👍 / 👎.
| if (!SKIP_UPDATE_NOTICE.has(command)) { | ||
| const check = await checkForUpdateCached(getVersion()); | ||
| if (check?.updateAvailable) { | ||
| console.log(`\n(sessionforge v${check.latestVersion} is available — run \`sessionforge update\` to install it.)`); |
There was a problem hiding this comment.
Keep update notices out of JSON stdout
When a released CLI is behind and a user invokes any JSON-producing command such as list --json, show --json, search --json, cleanup --json, or audit --json, this appends a human-readable update notice to stdout after the JSON document. That makes the documented structured output unparsable for scripts precisely when an update is available; suppress the notice for --json or send it to stderr.
Useful? React with 👍 / 👎.
목표
/goal implement new release detection and auto update (additionally manual update with cli also) cli+plugin both변경
sessionforge check-update/sessionforge update추가. 감지는 자동(24시간 캐시, 실패해도절대 안 던짐, 다른 명령 끝에 한 줄 알림), 실제 교체는 항상 명시적
update명령으로만 — 몰래 바이너리를바꾸지 않음. Windows는 실행 중인 exe를 바로 덮어쓸 수 없어
.old.exe로 rename 후 교체하는 표준 패턴 사용.scripts/package-plugin.mjs가 패키징된 번들에.sessionforge-version마커를 기록,paseo-status가 설치된 플러그인 버전과 실행 중인 CLI 버전의 드리프트를 보여줌.검증