# fix\(cli\): resolve projects with missing workspace directories · PR \#9885 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/9885)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: audit/project-removal-4851

Target branch: main

Head commit: e65c174605f0244e2752fa680c3723f67058ed71

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/9885)

[Commits](https://git.cafe/pingdotgg/t3code/pull/9885/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/9885/diffs?format=markdown)

## Description

---

## Problem

A registered project cannot be removed or renamed by its stored workspace path after that directory disappears. The CLI validates the filesystem path before matching the project record, then reports `ProjectNotFoundError` even when the selected environment still contains that project.

## Fix

Keep project-ID lookup first and preserve successful path normalization. If workspace validation fails, allow an exact match against an active project's stored workspace path. The shared lookup covers remove and rename. Existing `--force` handling, thread-deletion rules, IDs and environment selection are unchanged. No directories are recreated or deleted.

Related to [#4851](https://github.com/pingdotgg/t3code/issues/4851), but **do not close that issue**. This only fixes lookup within an environment that still has the project record. A replacement machine with an empty database cannot remove an old environment's project; forgetting orphaned client entries remains a separate product decision.

## Before and after

Tested against [b01771c2](https://github.com/pingdotgg/t3code/commit/b01771c2383bbcdf154b8c4e9f1a7d5e3867e2d0), committed September 4, 2026 at 20:31:16 PDT.

The tests create disposable Git directories and SQLite databases through the actual CLI parser, handlers and orchestration commands. They then move the workspace aside and assert that its original path is absent.

- Before: direct remove by the missing stored path fails with `ProjectNotFoundError`, `activeProjectCount: 1`, caused by `WorkspaceRootNotExistsError`. The final regression suite has one failing missing-path case and nine passing controls.
- After: all ten focused tests pass. Exact-path rename and force removal update the persisted project/thread records; the missing directory stays missing.
- Controls preserve empty-project removal by ID, non-force rejection with threads, force removal by ID, rename by ID, existing path normalization and separate symlink project entries. An unregistered symlink still cannot select another project's record.
- A replacement empty database still rejects the original ID and leaves the original database untouched.

Current web Settings and Legacy Sidebar removal callers already send `force: true` with the owning environment/project ID. They are unchanged. No browser or remote environment was used; CLI output and persisted-state assertions are the relevant evidence.

## Checks

```sh
cd apps/server
vp test run src/bin.test.ts src/cli/project.test.ts --maxWorkers 2 -t "project lookup with unavailable workspaces|adds, renames, and removes projects offline|force removes projects that still contain threads|maps declared server failures|preserves unexpected server failures"
../../node_modules/.bin/tsgo --noEmit
```

Ten focused tests, server typecheck, two-file lint and formatting pass. Typecheck reports pre-existing Effect suggestions outside this diff. The orchestrator independently reviewed the whole two-file diff and the remove/rename/WorkspacePaths context, then reran the ten focused actual CLI/SQLite tests successfully (15 unrelated tests skipped). All current-head executed CI, Correctness, Approvability, Effect Service Conventions and Bugbot passed; all review/comment pages were read with no unresolved findings. The exact reviewed head was squash-merged on September 4, 2026 at 20:40:51 PDT as [9cb40178](https://github.com/pingdotgg/t3code/commit/9cb40178a53cca279c67a9079afab3cddf6b6ddb). The checks watcher has finished.

Prepared by GPT 6 Astra via Codex in T3 Code.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Narrow lookup fallback in shared project CLI resolution with broad integration tests; no changes to deletion semantics or filesystem side effects beyond fixing identifier resolution.
> 
> **Overview**
> Fixes **`project remove`** and **`project rename`** so they can target a project by its **stored workspace path** even after that directory no longer exists on disk.
> 
> In **`findActiveProjectTarget`**, ID lookup is unchanged and successful path normalization still applies. When normalization fails (e.g. missing workspace), the CLI now matches the identifier against active projects’ persisted **`workspaceRoot`** using the normalized path if available, otherwise the trimmed input string. **`--force`**, thread-deletion rules, and cross-environment behavior are unchanged; an empty replacement database still cannot delete another environment’s project.
> 
> Adds CLI integration coverage in **`bin.test.ts`**: temp git workspaces, optional threads, moved-aside directories, rename/remove by ID and path, symlink vs stored-path identity, and replacement **`--base-dir`** rejection.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit e65c174605f0244e2752fa680c3723f67058ed71. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- Macroscope's pull request summary starts here -->
<!-- Macroscope will only edit the content between these invisible markers, and the markers themselves will not be visible in the GitHub rendered markdown. -->
<!-- If you delete either of the start / end markers from your PR's description, Macroscope will append its summary at the bottom of the description. -->
> [!NOTE]
> ### Fix `findActiveProjectTarget` to resolve projects with missing workspace directories
> Updates workspace-path matching in `findActiveProjectTarget` so that when path normalization fails (because the workspace directory is gone), the resolver falls back to comparing the trimmed input against the stored path. This lets project mutations like rename and removal proceed by ID or stored path after the workspace directory has been removed. Adds a comprehensive test suite in [bin.test.ts](https://github.com/pingdotgg/t3code/pull/9885/files#diff-94f2a1c34e7815e43d7fbf62ddcccb7ece13c7ec57e628dcfadbedb93c853a4d) covering unavailable workspace scenarios, force removal with child threads, replacement databases, normalized paths, and symlink aliases.
> - Risk: if a stored workspace path differs from the trimmed input in trivial ways (e.g. trailing slash) and normalization fails, the path will no longer match.
>
> <!-- Macroscope's review summary starts here -->
>
> <sup><a href="https://app.macroscope.com">Macroscope</a> summarized e65c174.</sup>
> <!-- Macroscope's review summary ends here -->
>
<!-- Macroscope's pull request summary ends here -->



