Add portreaper extension - #30075
Conversation
- 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
|
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. |
Greptile SummaryThe PR adds Portreaper, a macOS Raycast extension that downloads and verifies its Rust classification engine before scanning and managing orphaned development processes.
Confidence Score: 4/5The 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 Files Needing Attention: extensions/portreaper/src/search-ports.tsx, extensions/portreaper/src/install.ts Important Files Changed
Reviews (6): Last reviewed commit: "Update portreaper extension" | Re-trigger Greptile |
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,消除靠约定同步的重复
|
Thanks for the review — all three findings were valid and are fixed in 1. Schema-incompatible cached CLI could not be replaced. Correct, and this one was the real bug. 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 3. Preference title casing. Fixed: Verified with 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 |
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 提交状态与再提交的两个操作要点
Similar extensions already in the StoreAutomated first pass. Overlap is not a blocker on its own, but the README should make the difference clear.
|
|
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 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:
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 自动查重的性质、应对与三条踩坑
|
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 ( 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:
That framing does more work than the feature list did. The clearest example: Portreaper lists orphaned dev processes holding no port at all (an Also in this push: a new extension icon, matching a redesign of the desktop app's icon. |
| await rm(cliPath, { force: true }); | ||
| cliPath = await install(); |
There was a problem hiding this 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.
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)
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 runit launched keeps running — reparented to the OS, still holding port 3000. Next time younpm run devthe 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:
electron-vitemain process, adopted by launchd after its parentnodedied, is invisible to a port scan but is exactly the kind of residue worth clearing.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).launchd,brew services, orpm2, and anything installed in a standard location, is never 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:
fanhefeng/portreaper), built by a public GitHub Actions workflow — traceable build sources, not an opaque blob.portreaper-cli-SHA256SUMS; mismatch ⇒ file removed and an error surfaced.Same pattern as recently approved extensions: glean-search #28995, lumen #28909, and
speedtest.platformsis["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):Suspects/Healthygrouping with confidence tagsThe suspects shown are three deliberately orphaned
nodedev servers created for the capture; the list is filtered tonodeso the screenshots do not expose the unrelated applications running on the author's machine.Checklist
npm run buildand tested this distribution build in Raycastassetsfolder are used by the extension itselfREADMEare placed outside of themetadatafolderAdditionally 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.