Files
PaperClipAI/doc/project-repositories.md
T
DottaandPaperclip 24067a16b5 fix(workspaces): classify restricted shared repository admission (#16895)
## Thinking Path

> - Paperclip manages agents, their tasks, and their saved work.
> - Workspace preparation must check access before an agent starts.
> - A shared project checkout can contain another task's private
worktree.
> - The access guard correctly rejects a run that cannot read that work.
> - The isolated-source path reports this as a configuration blocker
after #16545.
> - Shared project repository admission missed that classification and
returned a generic setup failure.
> - This change uses the same blocker at that boundary so recovery names
the required workspace repair.

## Linked Issues or Issue Description

Refs #16545 and #16095. The open retained-reader repair in #16832
addresses deleted identities, a separate condition.

**What happened?**

A task reused a shared project workspace. Another worktree in that
checkout retained a private task that the run could not read. Repository
admission rejected access before provider startup. The run reported
`setup_failed`, and recovery showed a generic no-live-execution notice.

**Expected behavior**

Keep the access denial. Report `configuration_incomplete` with reason
`workspace_source_restricted`. Preserve the workspace and files. Give
the owner the existing workspace repair action. Do not queue an
automatic failure retry or report this expected blocker as an unexpected
Sentry failure.

**Steps to reproduce**

1. Bind a task to a shared project checkout.
2. Add a private task worktree beneath that checkout.
3. Continue the shared task as an agent that cannot read the private
task.
4. Inspect the run result and recovery action.

**Version and deployment**

Reproduced in the full heartbeat service from source revision
`06d4fe3121`, with PostgreSQL and real Git worktrees on macOS. The
provider never starts. The regression also covers the existing
Git-worktree path.

## What Changed

- Apply the existing restricted-source classifier to task repository
admission.
- Extend the heartbeat regression to shared project workspaces.
- Assert one run and one recovery action, no provider start, no
unexpected Sentry report, and preserved binding and files.
- Update the workspace documentation and both capability maps.

## Verification

- Red: the shared project regression returned `setup_failed`; the
Git-worktree control passed.
- Green: the same two cases pass after the change.
- `node scripts/check-maps.mjs` and `git diff --check` passed.
- Current head `e26cdd45da76c6edaa1622856f9c69e9f48b81fb`: 55 successful
checks, one neutral check, two skipped Storybook checks. Greptile 5/5,
no review threads, mergeable.
- All 337 tests passed across `heartbeat-workspace-branch-containment`,
`heartbeat/workspace-preparation`, `run-failure-report`, and
`recovery/stranded-notice`.
- `pnpm -r typecheck` and `pnpm build` passed. All full current-head CI
suites passed in [run
39315969013](https://github.com/paperclipai/paperclip/actions/runs/39315969013).
The duplicate local full Vitest run was stopped after 11 minutes when CI
completed; it has no local full-suite completion claim. The 337 affected
local tests completed successfully.
- Running-app acceptance at `e26cdd45da76c6edaa1622856f9c69e9f48b81fb`:
a synthetic private Git worktree caused the shared task to stop with the
visible Workspace source restricted notice and no process start. The
normal workspace API selected a separate task directory. Resolving the
recovery action resumed the same assignee. A real process fixture
completed, the output persisted after reload, and both source files
remained intact. Checkout and execution locks cleared. There were no
automatic failure retries.
- The first recovered process wrote its report to the command cwd. This
was a fixture output-path mistake. The corrected fixture reads the
selected workspace from the authenticated API before writing. One
explicit additional admission verified that output. The record retains
both successful process runs and the earlier configuration-blocked run.
This is local process-adapter acceptance, not live Codex/provider
qualification.
- Staging diagnosis confirmed a retained private source and failure
before provider startup. The original task has not been recovered. The
deployed UI hides its workspace selector, and no authenticated
task-write API is available in this session. No live Codex recovery is
claimed.

## Risks

The same exact 403 denial is now a configuration blocker during shared
repository admission. Other failures retain their original type. Access
policy, cloning, credentials, provider execution, and workspace contents
do not change. An existing task still requires a permitted workspace
repair before it can run.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, code execution, and browser
tools. The exact deployment identifier and context-window size are not
exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-19 16:03:16 -05:00

6.4 KiB

Project source repositories

The Create project dialog accepts a name and optional GitHub repository selections. It uses the same RepositoryEditor as project Configuration. Description remains editable in Configuration. Status, goal links, and target dates remain supported by the API but are omitted from creation; Status and Goals are omitted from Configuration. Old Overview URLs and saved Overview preferences redirect to Configuration.

API and persistence

  • GET /api/companies/:companyId/project-repositories returns repositories, connectionCount, and failedConnectionCount. Repository IDs are GitHub's stable numeric IDs represented as strings. Results are deduplicated across accessible grants and sorted by full name. Connection labels are display provenance only.
  • POST /api/companies/:companyId/projects accepts optional repositoryIds. The server resolves new selections through the caller's authorized GitHub grants before creating the project and all repository workspaces in a transaction. The existing workspace input remains supported; it cannot be combined with repositoryIds.
  • PUT /api/projects/:id/repositories accepts the selected repositoryIds array. Accessible retained IDs refresh their canonical name and URL after renames or transfers; unavailable retained IDs keep their saved metadata. Replacement is transactional. Existing selections may be retained or removed even if their GitHub connection becomes unavailable. New identities require current access. Legacy URL workspaces are preserved, and matching legacy URLs are adopted without creating a duplicate workspace. Local/remote workspace locations survive detaching their repository.

No schema migration is required. Selected repositories are normal project workspaces with metadata.githubRepositoryId. Existing manual repoUrl workspaces remain editable through Configuration and the workspace API. One workspace remains primary. Tasks materialize the other distinct repositories as editable checkouts inside their workspace, including when no local folders are configured. Local execution and sandbox staging use the same layout; sandbox restore preserves each repository's Git history. See Project Repository Checkouts for paths, ignore rules, and reuse behavior. Responsible-user credential rules still apply. A repository selection never delegates credentials.

Fresh tasks using the default repository workspace can clone the same authorized remote into a company- and task-scoped source directory when an existing project checkout contains another task's restricted workspace. The clone uses the run's Git connection and keeps separate Git metadata. Source access checks still apply before the task workspace is published. Low-trust review runs cannot use this persistent clone fallback. This fallback does not copy unpublished local changes or replace reused workspaces, explicit workspace selections, pinned branches, or repositories that only exist on the local filesystem. Configured branch templates must derive the branch from the task identifier, ID, title, or slug. Literal or task-independent templates retain the source denial, so a fresh clone cannot replace a selected local branch with a new empty branch. New implicit Git workspace defaults record their project-source provenance. An untyped checkout binding from an older task is ambiguous and keeps the denial, including explicit checkout IDs and inherited parent sources. Admission rechecks the current binding rather than inferring a default from the selected checkout ID. When the denial is retained, the run stops before the agent starts with a configuration_incomplete failure (reason workspace_source_restricted). The task is blocked with a notice that tells the owner to push unpublished work and select a new workspace, or to grant access to the restricted task, then retry. Paperclip does not report this owner action to Sentry as an unexpected run failure. This applies to both isolated-source checks and repository admission for a shared project workspace. Neither path replaces the existing workspace or retries the same access denial automatically. Startup validation of these recorded defaults keeps the task's effective workspace strategy. It does not substitute the agent's shared strategy or grant project writes. Workspace close and reopen use the recorded isolated source. Both operations verify its company, task, repository URL, and separate Git storage before use.

Discovery and setup

The server checks company membership, grant ownership/status, and organization-grant audiences before loading provider metadata. Connection managers receive no bypass to another person's personal repositories. Managed GitHub grants refresh installation access; PAT connections use paginated /user/repos. Provider failures are reported without exposing provider error bodies or credential material. Successful connections remain selectable when another connection fails.

ConnectionSetupFlow owns provider setup in both Apps and project dialogs. Task intents retain their existing callback protocol. Standalone dialogs verify the saved connection through the API after the sign-in popup returns to the instance. Project name and repository drafts stay mounted across setup and cancellation.

UI review and verification

Proposals/Project repos contains the reviewed states, including loading, failure, empty search, disconnected GitHub, multiple repos, legacy URLs, forty selections, mobile, and short viewports. The configuration story composes the production page properties through an explicit repositories slot. Story setup and saves use fixtures.

  • Shared visual control: ui/src/components/RepositoryEditor.tsx.
  • Data and error handling: ProjectRepositoryInput.tsx.
  • Configuration persistence and legacy editing: ProjectRepositories.tsx and LegacyProjectRepository.tsx.
  • Production dialog: NewProjectDialog.tsx.
  • Server tests: project-repositories.test.ts and project-repositories-persistence.test.ts.
  • Browser acceptance: tests/e2e/project-repositories.spec.ts.

The browser suite uses a real temporary server and database. It verifies creation, forty persisted repos, mobile scrolling, removal/save/reload, legacy URL editing, and rejection without a partial project. Provider discovery is simulated in the picker rejection test. GitHub network and popup behavior use deterministic fixtures in integration/component tests; the suite does not authorize a real GitHub account.