Skip to content

feat: detect missing project folders + offer GitHub re-clone - #273

Open
NovakPAai wants to merge 1 commit into
mainfrom
feat/missing-project-detection
Open

feat: detect missing project folders + offer GitHub re-clone#273
NovakPAai wants to merge 1 commit into
mainfrom
feat/missing-project-detection

Conversation

@NovakPAai

Copy link
Copy Markdown
Collaborator

What & why

When you register/clone a project in codbash and later delete that folder from disk (cleaning up your machine), the Projects launcher kept showing the tile, and clicking New/Last failed with a confusing "invalid or unsafe project path". This adds first-class handling for missing folders.

Note: the new exists check already flags real registered projects as missing-on-disk in local testing, so the feature is immediately useful.

Behaviour

  • On the tile: a deleted folder gets a dashed-red card + a role="note" disclaimer — "⚠ Folder is missing on disk — it was moved or deleted." Launch controls are replaced by ↓ Re-clone (when a GitHub remote is known) and × Remove. History drill-in stays available. Local projects with no GitHub remote show "Restore the folder, or remove it from the list."
  • On launch: /api/launch returns { missing:true, remoteUrl, projectId } (before the generic safety check), so every launch path — the launcher's New/Last and the session-detail Resume — pops a one-click re-clone dialog instead of a dead-end toast.
  • Re-clone: POST /api/projects/reclone { id } restores the repo at its original path (not a fresh ~/code/<repo>), reusing cloneRepo.

API

Route Change
GET /api/projects/manual + exists per project (skips git probe when the folder is gone)
POST /api/launch returns missing:true (+ remoteUrl, projectId) for a deleted folder
POST /api/projects/reclone new — re-clone a registered project to its original path; per-id in-flight guard → 409

Review (3 agents: code / security / UX — all findings applied)

  • Security MED — closed a symlinked-parent escape of cloneRepo's home-dir containment (realpath-of-nearest-ancestor check), reachable precisely via the "folder missing" window. Plus: anchored GitHub-remote regex (rejects control chars), pathExists length cap, per-id re-clone rate guard, anchored client-side isGithubRemote().
  • Code HIGH — session-detail Resume (detail.js) now also handles missing:true.
  • UX — AA-contrast disclaimer text, role="note", confirm-overlay dialog semantics (role="dialog"/aria-modal/aria-labelledby), Escape-to-close, focus-on-open, aria-busy, immediate "Cloning…" feedback.

Deferred (documented in the design doc)

  • HTTP-level route tests: startServer has no teardown seam (autoSync/heartbeat timers keep the loop alive) — needs a route-extraction refactor. Interim evidence: green end-to-end server smoke + unit tests for pathExists/cloneRepo.
  • Repo-wide CSRF Origin check on mutating POST routes — pre-existing/systemic, not introduced here.

Test plan

  • node --test test/*.test.js — 216 pass / 0 fail (1 win32-only skip); adds test/projects-missing.test.js (10 cases incl. symlink-ancestor + control-char guards)
  • End-to-end server smoke — register → delete folder → exists:false → launch missing:true → reclone guardrails (no-remote 400, invalid-id 400, unknown-id 404) — GREEN; user registry backed up + restored intact
  • Headless render check of the real renderLauncherCard — missing (GitHub + local) / present cards + anchored isGithubRemote — GREEN
  • Manual: delete a cloned repo's folder, confirm the tile disclaimer + one-click re-clone in the browser (browser extension wasn't available in this session)

Docs: docs/design/missing-project-detection.md, specs/missing-project-detection.feature.

🤖 Generated with Claude Code

When a registered project folder is deleted/moved on disk, the Projects
launcher now flags it instead of failing with a confusing "invalid path".

- projects.js: pathExists() on-disk check; cloneRepo hardening — anchored
  GitHub-remote regex + realpath-of-nearest-ancestor containment guard
  (closes a symlinked-parent escape reachable via the "folder missing" window)
- server.js: GET /api/projects/manual returns `exists`; /api/launch returns
  {missing:true, remoteUrl, projectId} before the generic safety check; new
  POST /api/projects/reclone restores the folder at its original path, with a
  per-id in-flight guard (409)
- frontend: missing tiles show a role="note" disclaimer + Re-clone/Remove;
  launch-time misses (app.js + detail.js resume) offer a re-clone dialog;
  shared anchored isGithubRemote(); a11y — dialog semantics, Escape close,
  focus-on-open, aria-busy, AA-contrast warning text
- tests: pathExists + cloneRepo guardrails (symlink-ancestor, control chars);
  headless render check in scratchpad; end-to-end server smoke green
@NovakPAai NovakPAai added the enhancement New feature or request label Jul 24, 2026
@NovakPAai
NovakPAai requested a review from vakovalskii July 24, 2026 10:58
@NovakPAai

Copy link
Copy Markdown
Collaborator Author

@vakovalskii — requesting your review as maintainer. 🙏

TL;DR: the Projects launcher now detects when a registered project folder was deleted/moved on disk and offers a one-click Re-clone from GitHub (restoring it at its original path), instead of failing with a confusing "invalid path".

Safe to review quickly:

  • CI is green (6/6 — ubuntu + macOS × Node 18/20/22).
  • Zero new core dependencies (Node stdlib only), consistent with the project's zero-dep policy.
  • Rebased directly onto main — the diff is exactly 9 files, no unrelated commits.

Please pay attention to:

  1. src/projects.js — the cloneRepo hardening: anchored GitHub-remote regex + realpath-of-nearest-ancestor containment check (closes a symlinked-parent escape reachable via the "folder missing" window). This is the one security-relevant change.
  2. POST /api/projects/reclone in src/server.js — re-clones into the project's original registered path (not a fresh ~/code/<repo>), with a per-id in-flight guard (409).
  3. Whether the missing:true contract on /api/launch (handled in both app.js and detail.js) reads cleanly to you.

Two items are intentionally deferred (documented in docs/design/missing-project-detection.md): HTTP-level route tests (blocked on a startServer teardown seam) and a repo-wide CSRF Origin check (pre-existing, not introduced here). Happy to take either as a follow-up or fold in now — your call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants