Skip to content

Add portreaper extension - #30075

Open
fanhefeng wants to merge 6 commits into
raycast:mainfrom
fanhefeng:ext/portreaper
Open

Add portreaper extension#30075
fanhefeng wants to merge 6 commits into
raycast:mainfrom
fanhefeng:ext/portreaper

Conversation

@fanhefeng

@fanhefeng fanhefeng commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Description

Portreaper finds orphaned dev-server processes squatting on your ports and lets you reap them from Raycast.

You kill a terminal, but the vite / node / cargo run it launched keeps running — reparented to the OS, still holding port 3000. Next time you npm run dev the port is "already in use" and you have no idea which ghost to kill.

This is not a generic port viewer. Its job is to decide which listeners are orphaned dev-server zombies, so you act on a verdict instead of a raw process list:

  • Every TCP listener, plus orphaned dev processes that hold no port at all — a leftover electron-vite main process, adopted by launchd after its parent node died, is invisible to a port scan but is exactly the kind of residue worth clearing.
  • A verdict with its evidence. Suspects are tiered confirmed / likely / possible, and each row shows the signals behind the call (reparented to PID 1, launcher chain ends at a dead shell, dead terminal session, dev-server command line, duplicate instance of the same project).
  • Exemptions applied automatically — anything managed by launchd, brew services, or pm2, and anything installed in a standard location, is never flagged.
  • Star to exempt permanently. A daemon you detached on purpose is behaviorally identical to an accidental zombie; star it once and it stops being flagged.

Terminating always asks for confirmation, and the engine re-checks the process creation time immediately before killing — if it moved, the kill is refused. That closes the window where a PID gets recycled between looking at the list and pressing Enter.

The classification engine is shared with the Portreaper macOS menubar app; both frontends read the same whitelist file, so a star added here shows up in the desktop app's next scan and vice versa. The desktop app is optional — this extension works standalone.

About the binary dependency (please read)

The classification logic lives in a small Rust CLI, portreaper-cli. On first run the extension downloads it and verifies it against the project's published SHA-256 checksum; a binary that fails verification is deleted, never executed. Nothing is installed system-wide — it lives in the extension's own support directory, and the UI states plainly what it is doing while downloading.

This follows the "Binary Dependencies" guidance — downloaded from a trusted location with hash verification, rather than bundling an opaque binary:

  • Source: the project's own GitHub Releases (fanhefeng/portreaper), built by a public GitHub Actions workflow — traceable build sources, not an opaque blob.
  • Integrity: SHA-256 checked against the release's published portreaper-cli-SHA256SUMS; mismatch ⇒ file removed and an error surfaced.
  • Self-healing: if the cached copy later fails to execute (truncated download, arch change, lost exec bit) it is removed and re-fetched once.
  • No user action required — the guidance explicitly asks extensions to avoid making users perform additional downloads.

Same pattern as recently approved extensions: glean-search #28995, lumen #28909, and speedtest.

platforms is ["macOS"] only: the CLI's Windows leg has no manual QA yet, so the extension does not claim support it cannot back.

Screencast

Screenshots are in metadata/ (light theme, 2000×1250):

  1. Filtered list — Suspects / Healthy grouping with confidence tags
  2. Detail panel — the signals behind a verdict, PID, category, ports, uptime
  3. Action panel — Terminate / Force Kill styled as destructive, plus Star
  4. Confirmation dialog — names the process, PID and port before terminating

The suspects shown are three deliberately orphaned node dev servers created for the capture; the list is filtered to node so the screenshots do not expose the unrelated applications running on the author's machine.

Checklist

Additionally verified on a real machine: first-run download + SHA-256 verification end to end (all cached and local copies removed to simulate a fresh install — the re-downloaded binary's digest matched the published manifest byte for byte), scan and grouping, detail panel, action panel, terminate confirmation, search filtering, and the no-port orphan case.

- docs: 同步 CLAUDE.md / coderabbit,新增 Raycast 维护笔记
- feat(raycast): 备好 Store 上架,修过滤计数与依赖
- ci: 补两个一致性守卫,把 Raycast 扩展纳入门禁
- refactor(web): 抽出 useScan 与弹窗组件,收敛 props 与动态 i18n 键
- refactor(core): 拆分 scanner/mod.rs,消除靠约定同步的重复
- ci(pages): 注记 dispatch 通道积压时的 push 触发后备
- ci: 加 workflow_dispatch 手动触发口
- chore(deps): bump tauri-apps/tauri-action from 0.6.2 to 1.0.0 (raycast#14)
- chore(deps): bump pnpm/action-setup from 6.0.8 to 6.0.9 (raycast#10)
- chore(deps): bump actions/setup-node from 6.4.0 to 7.0.0 (raycast#22)
- chore(deps): bump dtolnay/rust-toolchain (raycast#23)
- chore(deps): bump actions/checkout from 6.0.3 to 7.0.1 (raycast#24)
- chore(deps): log 0.4.33、tauri-build 2.6.3
- chore: v0.8.2 全面审查修复——误杀面、平台对齐、守卫链与可达性 (raycast#29)
- chore(release): v0.8.1
- fix(release): portreaper-cli 版本跟随应用,不再自成一套
- refactor(core): 判定引擎下沉为 portreaper-core,新增 CLI 与 Raycast 扩展 (raycast#28)
- docs: 新增 core 拆分与多前端适配的架构设计稿
- refactor(ui): App.tsx 拆出 src/components/ —— 容器与呈现分离
- fix(scanner): 证据列表按事实取证 —— NonstandardPath 不再说谎,OrphanedChain 不再同义反复
- build(hooks): 补上 Rust 侧的本地门禁,根治「push 后 CI 才报红」
- style(scanner): 应用 rustfmt;CLAUDE.md 命令清单补上 cargo fmt --check
- chore(release): v0.7.2
- feat(scanner): 检出 /Applications 里的 headless 自动化浏览器实例(Gap 1)
- docs: 安装步骤新增 dmg 内置解除隔离脚本的推荐路径(v0.7.1 起附带)
- chore(release): v0.7.1
- feat(macos): LSUIElement 消除冷启动 Dock 闪烁;dmg 内置解除隔离助手
- fix(tooling): 修复 v0.7.0 发版暴露的 bump 格式回退与 Dependabot npm 失效
- chore(release): v0.7.0
- docs(website): 功能卡补充 v0.6.0 已发布的无端口孤儿与重复 dev server 检测
- feat(macos): Accessory 菜单栏化;refactor: 全仓审查修复 18 项
- chore(toolchain): 迁移到 TypeScript 7 与 Vite+ 全家桶
- docs: 补充 macOS「已损坏」与 dmg 直接运行的解隔离说明
- chore(release): v0.6.0
- feat: 托盘/应用菜单新增数据/缓存/临时目录入口,修复前端日志自激循环
- chore(release): v0.5.2
- feat: 日志落盘与图标重设计,并修复扫描器边界缺陷、清理冗余
- chore(deps): bump the react group across 1 directory with 3 updates (raycast#8)
- chore(deps-dev): bump happy-dom from 18.0.1 to 20.10.2 (raycast#7)
- chore(deps-dev): bump vite from 7.3.3 to 8.0.16 (raycast#9)
- chore(deps-dev): bump vitest from 3.2.6 to 4.1.8 (raycast#4)
- fix(tsconfig): 显式声明 types: ["node"](TS6 不再自动包含 @types)
- chore: 显式声明 @types/node;Dependabot 给 react 全家桶分组
- chore(deps-dev): bump typescript from 5.8.3 to 6.0.3 (raycast#6)
- chore(deps-dev): bump @vitejs/plugin-react from 4.7.0 to 5.2.0 (raycast#2)
- ci: Dependabot 对 sysinfo/windows 只收 patch 级更新
- refactor: 全项目审计修复(高/中/低全档)—— 分类确定性、供应链钉死、可观测性、前端纯函数层
- revert: 回退 E6 windows 宏合并(Windows CI clippy -D warnings 失败),保留 E5;windows.rs 恢复 914592d 已验证版本
- docs: 记录工具链钉版本架构与升级要点(CLAUDE.md + rust-toolchain.toml)
- refactor: 全项目审计 D/E 档清理(去重 / 一致性 / 健壮性)
- fix(classify): 链孤儿对称享受「会话已死」佐证,对齐 orphan×dead-session→Confirmed
- fix(ui): scan_ports invoke 加超时,防后端卡死导致轮询永久冻结
- ci: 以 .node-version 作为 Node 版本单一真相源
- ci: 升级 GitHub Actions 至 Node 24 运行时,消除弃用警告
- fix(ci): 钉死 rust 工具链 1.96.0,根治 rustfmt 漂移导致的格式检查失败
- feat(scanner): detect orphaned dev processes that hold no port
- style: rustfmt 换行 is_dev_build_artifact / is_whitelisted 的 || 链
- chore(release): v0.5.1
- fix: review-round hardening across scanner, whitelist, and CSP guard
- chore(release): v0.5.0
- chore: drop unreferenced scaffold assets (tauri.svg, react.svg)
- docs(lib): correct menu-event routing comment — safety is id-disjointness
- fix(release): mark pre-release tags as GitHub prereleases; anchor experimental section
- fix(macos): boundary-safe slicing in parse_lsof
- fix(macos): Cmd-Q hides to tray via custom app menu — terminate: was uninterceptable
- fix(ui): whitelist toggle uses freshScan, not a stale in-flight poll
- fix(test): hold kill-resolver in an object to dodge tsc closure narrowing
- fix(windows): log GetExtendedTcpTable failures instead of swallowing them
- fix(security): enable production CSP and scope opener to localhost
- docs: point de-experimental steps at the real release.yml location
- fix(ui): mirror whitelist key, show full command, narrow brand match, guard sweep
- fix(macos): honor tray-only-quit on Cmd-Q
- fix(macos): pin absolute paths for system subprocess calls
- fix(scanner): whitelist-key collapse, pm2 markers, eval cutoff, dup-guard symmetry
- fix(windows): pull cmd/cwd/user in process refresh — dev detection was dead
- fix(ui): language-neutral failure separator, sweep-time button guard
- fix(scanner): entry-script identity, go-build classification, win time sanitizing
- fix(ui): clear stale failure banner when a later action succeeds
- docs: fix stale comments left behind by the review-fix batch
- fix(ci): add required packages field to pnpm-workspace.yaml
- fix(windows): size TCP table buffer to the full struct, not 16 bytes
- fix(whitelist): atomic writes, corruption backup, and error propagation
- feat(guard): close the i18n render-path blind spot in the parity check
- fix(scanner): stop short dev keywords from matching accidental substrings
- fix(ui): keep action errors visible across background polls
- chore(release): v0.4.0
- feat(scanner): duplicate dev-server detection (same project, multiple instances)
- chore(release): v0.3.1
- fix(scanner): detect orphaned interpreter `-m` module servers (python -m http.server)
- docs: add real app screenshot; match full command in process knowledge base
- chore(release): v0.3.0
- feat(ui): redesign for clarity — verdict-grouped list with plain-language stories
- fix(release): comment out signing env — empty APPLE_CERTIFICATE breaks macOS bundling
- chore(release): v0.2.0
- fix(ci): gate macOS-only chain test fixtures to macOS target
- test(platform): live verification of kill identity check (3 paths)
- fix(review): apply confirmed findings from six-lens adversarial review
- feat(website): bilingual download site for GitHub Pages
- docs: bilingual README, releasing runbook, Windows test checklist
- ci: GitHub Actions CI, tag-triggered release, Pages deploy + version tooling
- feat(ui): bilingual i18n, confidence badges, platform-aware kill UI
- feat(scanner): cross-platform architecture + zombie classification v2
- chore: baseline snapshot of macOS-only Portreaper 0.1.0
@raycastbot raycastbot added new extension Label for PRs with new extensions platform: macOS labels Aug 8, 2026
@raycastbot

Copy link
Copy Markdown
Collaborator

Congratulations on your new Raycast extension! 🚀

We're currently experiencing a high volume of incoming requests. As a result, the initial review may take up to 15 business days.

Once the PR is approved and merged, the extension will be available on our Store.

@fanhefeng
fanhefeng marked this pull request as ready for review August 8, 2026 02:13
@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds Portreaper, a macOS Raycast extension that downloads and verifies its Rust classification engine before scanning and managing orphaned development processes.

  • Adds process scanning, verdict filtering, details, termination, and shared-whitelist actions.
  • Adds managed CLI discovery, verified installation, compatibility recovery, and user-facing failure states.
  • Adds the extension manifest, documentation, changelog, icon, and Store screenshots.

Confidence Score: 4/5

The PR does not yet appear safe to merge because concurrent recovery can remove a newly installed CLI and leave scans failing or the managed executable absent.

The previously reported recovery race remains: stale and current load() calls independently remove and reinstall the same managed path, while request IDs protect only React state updates and do not coordinate those filesystem operations.

Files Needing Attention: extensions/portreaper/src/search-ports.tsx, extensions/portreaper/src/install.ts

Important Files Changed

Filename Overview
extensions/portreaper/src/search-ports.tsx Implements the primary UI and process-management lifecycle; overlapping managed-CLI recoveries still mutate one shared executable path without serialization.
extensions/portreaper/src/install.ts Downloads, checksum-verifies, and atomically installs the platform-specific CLI, but installation is not coordinated across concurrent callers.
extensions/portreaper/src/cli.ts Defines the CLI schema, discovery, scan, termination, and whitelist process boundary.
extensions/portreaper/package.json Defines a macOS view command, valid extension metadata, dependencies, and the corrected title-cased CLI-path preference.
extensions/portreaper/CHANGELOG.md Adds an initial release entry using the required merge-date placeholder.

Reviews (6): Last reviewed commit: "Update portreaper extension" | Re-trigger Greptile

Comment thread extensions/portreaper/src/search-ports.tsx Outdated
Comment thread extensions/portreaper/src/search-ports.tsx Outdated
Comment thread extensions/portreaper/package.json Outdated
fanhefeng added a commit to fanhefeng/portreaper that referenced this pull request Aug 8, 2026
raycast/extensions#30075 上机器人评审提的三条,逐条核对后都成立:

1. **schema 不兼容的托管副本换不掉**(最实质的一条)。`verifyCli` 只跑
   `--version`,一份陈旧但能跑的托管二进制照样通过,要到 `scan` 才被 schema
   拒绝 —— 而错误页的 Retry 每次都重新选中同一份,用户视角是死循环。
   改为在 SchemaMismatchError 上取一份最新的重试,与既有的「换掉不可用副本」
   同一套路。两处收紧:只换**我们自己下载的那份**(用户在偏好里显式指定的路径
   不擅自删),且**只换一次** —— 新下的仍对不上说明扩展与已发布 CLI 确实不同代,
   那是真错误,必须如实报出来,绝不无限重下。

2. **没有身份令牌的行仍然摆着终止入口**。维护笔记里早写着「没有令牌的行不该
   提供终止入口……让按钮先消失体验更好」,但代码没做到:动作照常渲染,点下去
   只能拿到一句失败提示,而它长得和能用的那个一模一样。现按 start_unix 隐藏整个
   区块。`doKill` 里的检查照旧保留 —— 这里管「不呈现」,那里管「即便被呈现出来
   也绝不放行」,两层各有其职。

3. **偏好项标题不符合 Store 的 title case 约定**:`portreaper-cli path`
   → `Portreaper CLI Path`(raycast-env.d.ts 由 ray build 同步重生成)。

验证:tsc --noEmit / ray lint / ray build 全过。

顺带把跨端 ★ 同步的**修复后**真机复验结果补进维护笔记(此前只记了「已修」),
含复验时踩到的坑:别拿 release 版 CLI 去验 pnpm tauri dev —— 分环境隔离下两者
读的不是同一个 whitelist.json,会看起来像 bug 复发。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- docs(raycast): 偏好项改名的连带更新
- fix(raycast): 处理 Store PR 的三条评审意见
- fix(deps): 补齐 @tauri-apps/plugin-log,并加守卫钉住 Rust/npm 版本配对
- chore(release): v0.9.0
- chore: 清依赖队列 + kill 错误统一结构化 wire 形态 (raycast#41)
- chore(deps): bump dtolnay/rust-toolchain (raycast#37)
- chore(deps): bump pnpm/action-setup from 6.0.9 to 6.0.10 (raycast#38)
- docs: CLAUDE.md 里的 typecheck 命令补全路径(省略号复制不能用)
- docs: 把「扩展英文单语」写进评审规则,止住反复误报
- fix(raycast): 断网时落到引导页,而不是甩一句 fetch failed
- chore: 撤掉 integrations/raycast 的 Dependabot npm 配置
- fix(web): kill 的禁用态按 PID 成集,修评审第二轮三条
- fix(raycast): Force Kill 判断改为失败关闭,修正评审指出的文档失真
- docs: 同步 CLAUDE.md / coderabbit,新增 Raycast 维护笔记
- feat(raycast): 备好 Store 上架,修过滤计数与依赖
- ci: 补两个一致性守卫,把 Raycast 扩展纳入门禁
- refactor(web): 抽出 useScan 与弹窗组件,收敛 props 与动态 i18n 键
- refactor(core): 拆分 scanner/mod.rs,消除靠约定同步的重复
- docs: 同步 CLAUDE.md / coderabbit,新增 Raycast 维护笔记
- feat(raycast): 备好 Store 上架,修过滤计数与依赖
- ci: 补两个一致性守卫,把 Raycast 扩展纳入门禁
- refactor(web): 抽出 useScan 与弹窗组件,收敛 props 与动态 i18n 键
- refactor(core): 拆分 scanner/mod.rs,消除靠约定同步的重复
@fanhefeng

Copy link
Copy Markdown
Contributor Author

Thanks for the review — all three findings were valid and are fixed in 22a742f0.

1. Schema-incompatible cached CLI could not be replaced. Correct, and this one was the real bug. verifyCli() only runs --version, so a stale-but-runnable managed copy passes; scan() then rejects the schema and Retry keeps selecting the same binary — a dead end from the user's side. Now a SchemaMismatchError fetches a fresh copy, reusing the existing replace an unusable copy path. Two deliberate limits: it only replaces our own downloaded copy (a user-specified path is never deleted), and it retries once — if the fresh download still mismatches, the extension and the published CLI genuinely are different generations, and that must be reported rather than looped on.

2. Terminate/Force Kill shown for entries without an identity token. Correct. The maintainer notes already said those rows should not offer a kill entry point, but the code never implemented it — the actions rendered identically to working ones and could only produce a failure toast. The whole section is now gated on start_unix != null. The check inside doKill() stays: this gate is about not presenting the action, that one is about never proceeding even if some future call site presents it.

3. Preference title casing. Fixed: Portreaper CLI Path. The README and maintainer notes referenced the old label by name, so those were updated too — docs pointing at a label that no longer exists in the UI are worse than no docs.

Verified with tsc --noEmit, ray lint and ray build.

Unrelated but worth noting for reviewers: the engine this extension downloads was released as v0.9.0 today, which fixes a cross-frontend bug where a star added here stayed invisible to the desktop app — the starred process kept counting as a suspect there and stayed in the one-click sweep target set. The extension pulls from releases/latest, so no change was needed on this side.

fanhefeng added a commit to fanhefeng/portreaper that referenced this pull request Aug 8, 2026
Store checklist 最后一项落地:raycast/extensions#30075 已提交并更新,
等待人工评审。机器人评审的三条与处置记在同一处。

另记两条踩过的:ray publish 要求工作区干净(直接报错退出);
修完重跑会更新同一个 PR 而非开新的,它靠本地 tag 记状态 —— 而那个 tag
会被 git push --tags 误推到公开仓库(本次已误推并删除)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- chore(release): v0.10.0
- Merge pull request raycast#42: 挂起进程终止不掉的根因修复 + 两个前端补终止后确认 + Raycast UI 重做
- fix(guard): 身份容差不只要三处相等,还要等于 5
- fix: 处理 CodeRabbit 的 7 条评审意见
- docs(macos): 记下抢焦点的两个测量陷阱与被否决的策略抬升方案
- docs: 同步本轮的不变量、评审规则与已知缺口
- feat(raycast): 终止后确认存活,并按 Raycast 规范重做列表与详情
- feat(web): 终止后确认进程真的没了,并补上四态空态与崩溃兜底
- feat(tauri): panic hook、单实例、窗口状态记忆与 bundle 元数据
- fix(core): 挂起的进程温和终止收不到信号,补发 SIGCONT
- fix(macos): 启动时把窗口抢到最前
- docs(raycast): 记录 Store 提交状态与再提交的两个操作要点
@pernielsentikaer

Copy link
Copy Markdown
Collaborator

Similar extensions already in the Store

Automated first pass. Overlap is not a blocker on its own, but the README should make the difference clear.

  • Port Manager (ports, 1,285 installs by @diegoleteliers10) — Monitor and manage open ports on your system with the ability to kill processes using specific ports.
    similarity 0.56

@fanhefeng

Copy link
Copy Markdown
Contributor Author

Thanks — the README now has a "How this differs from a port viewer" section that names Port Manager directly and links to its Store page.

Two things worth putting in front of the human reviewer:

The two extensions never meet. Port Manager declares "platforms": ["Windows"]; Portreaper declares "platforms": ["macOS"]. A macOS user does not see Port Manager in the Store, and vice versa. That is not an argument for accepting this one — just a fact that bounds the overlap.

The overlap that does exist is the list, not the product. Both render listening TCP ports and offer to kill what is behind one. Everything this extension is for sits after that point:

  • A verdict, not a list. Each row is classified confirmed / likely / possible from orphan signals — reparented to PID 1, launcher chain terminating at a dead shell, dead tty session, duplicate instance of the same project — with hard exemptions for anything launchd, brew services, or pm2 already manages. A dev server with a live terminal behind it is never flagged. That classification, not the port list, is the deliverable.
  • Processes with no port. It also lists orphaned dev processes holding no port at all — an electron-vite main process adopted by launchd after its parent node died, for example — which a port scan cannot reach by construction.
  • Guarded termination. The engine captures each process's creation time during the scan and re-reads it in the instant before signalling; a drift means the PID was recycled and the kill is refused. After the signal it re-scans until the process is actually gone (~2.5s deadline) and reports "still running" honestly rather than painting a checkmark over a survivor. A suspended process (Ctrl-Z) also gets a SIGCONT right after SIGTERM — a caught SIGTERM is never processed while stopped, which is the single most common "I killed it and nothing happened" case.
  • Shared engine. The classification lives in a Rust binary that the Portreaper desktop app drives as well, over one shared whitelist file, so a star added here is honoured in the desktop app's next scan.

The README section says all of this in user terms, including the line that matters most to someone choosing between the two: if you only want to free port 3000, a plain port viewer is the simpler tool.

- chore(icons): 应用图标重做,配色转向绿色系 (raycast#46)
- docs(raycast): 差异化一节改为正面定位,并修掉 404 的 Store 链接 (raycast#45)
- fix: 补上从未被类型检查的两个构建配置文件,收拢 build_entry 的九个位置参数 (raycast#44)
- chore(deps): bump Swatinem/rust-cache (raycast#43)
- docs(raycast): 记下 Store 自动查重的性质、应对与三条踩坑
@fanhefeng

Copy link
Copy Markdown
Contributor Author

Updated again — a correction and a rewrite of the section from my last comment, plus a new icon.

The Port Manager link in that section was broken, and I should flag that directly. I built the URL from the GitHub handle in the duplicate-check comment (diegoleteliers10), but Store pages use the author field — the correct one is raycast.com/dleteliers_/ports. So when I said the README "links to its Store page," anyone who clicked it got a 404. Fixed and verified.

The section itself is rewritten. The first version conceded twice — at the top ("if that is your whole problem, a plain port viewer is the simpler tool") and again at the bottom ("if what you want is a list of ports, this is a heavy way to get one") — with a feature comparison in between. That reads as "yes, this overlaps, but it also does these extra things," which does not actually answer the duplicate question.

It now frames the two as answering different questions:

A port viewer answers "what is on port 3000?" — you already know something is in your way, and you need to find it and free it.

Portreaper answers "what is still running that nobody is responsible for?" — you are not looking up a port you already care about; you are finding out what the last three days of development left behind.

That framing does more work than the feature list did. The clearest example: Portreaper lists orphaned dev processes holding no port at all (an electron-vite main process adopted by launchd, listening on nothing). Under "free this port" that is out of scope by definition; under "what did I leave behind" it is the whole point. The concession is now half a sentence, and the section closes on the distinction rather than on self-deprecation.

Also in this push: a new extension icon, matching a redesign of the desktop app's icon.

Comment on lines +227 to +228
await rm(cliPath, { force: true });
cliPath = await install();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Concurrent recovery deletes the CLI

If two refresh, retry, or post-action loads overlap while the managed CLI is incompatible, both requests can delete and replace the same path because the stale-request check does not guard these filesystem mutations. A stale request can remove the newer request's successful installation, and a subsequent download failure leaves the extension without a CLI while scans during the deletion window fail with ENOENT.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/portreaper/src/search-ports.tsx
Line: 227-228

Comment:
**Concurrent recovery deletes the CLI**

If two refresh, retry, or post-action loads overlap while the managed CLI is incompatible, both requests can delete and replace the same path because the stale-request check does not guard these filesystem mutations. A stale request can remove the newer request's successful installation, and a subsequent download failure leaves the extension without a CLI while scans during the deletion window fail with `ENOENT`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

- chore(raycast): 按新图标重出四张 Store metadata 截图
- chore(icons): 托盘图标与截图脚本配色跟上新镰环设计
- docs(raycast): 按 raycast#45 评审反馈修正两处与引擎行为不符的表述 (raycast#47)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new extension Label for PRs with new extensions platform: macOS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants