Commit Graph
6 Commits
Author SHA1 Message Date
DottaandPaperclip b94963f91e fix: retry temporary repository catalog failures before dispatch (#19690)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - A task must check repository access before it starts agent work.
> - Repository discovery can fail while the user's existing grant
remains valid.
> - Discovery previously replaced a known temporary provider refusal
with an empty catalog.
> - The task then reported lost access and did not schedule recovery.
> - This change keeps temporary discovery failures distinct and uses the
existing bounded retry owner.
> - Each retry must pass a fresh access check before it can use the
saved repository.

## Linked Issues or Issue Description

**What happened?**

A task with a saved catalog repository stops with an access denial when
GitHub returns an exhausted-quota response or HTTP 503. The task does
not start provider work, but the discovery layer discards the temporary
failure and returns an empty catalog.

**Expected behavior**

Known temporary failures before provider dispatch use the existing
bounded infrastructure retry. The successor checks the same responsible
user's current grants and reads the saved files only after fresh
discovery succeeds. A completed denial remains terminal.

**Steps to reproduce**

1. Bind a task to an authorized catalog repository with saved local
changes.
2. Make its authorized GitHub discovery return HTTP 403 with zero
remaining quota, or HTTP 503.
3. Let discovery recover before the existing retry deadline.
4. Observe one successful successor using the original repository bytes.

**Paperclip version or commit**

Reproduced on master at `b8d94b3ad1`. A search found no existing PR for
this temporary catalog failure.

## What Changed

- Keep a safe typed failure for known HTTP availability and quota
responses. Honor the later provider deadline and bound it to one day.
- Propagate that failure only when fresh discovery cannot find a
required repository. Preserve successful partial catalogs and the
existing connection setup error contract.
- Classify the failure only before provider dispatch. Reuse existing
bounded retry, interaction ownership, and bootstrap settlement rules.
- Keep current grant checks, saved repository selection, normal Stop,
and provider-started failure policy.
- Keep provider deadlines when accepted interactions use their own retry
owner. Recognize secondary HTTP 403 rate limits with valid retry advice
while keeping ordinary permission denials terminal. This matches [GitHub
rate-limit
guidance](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api).
- Keep direct repository request responses outside the automatic
bootstrap retry contract. Test successful partial discovery and
missing-required-repository recovery with two independent eligible
connections.
- Document task repository discovery recovery.

## Verification

- Behavioral RED: real heartbeat and PostgreSQL fixtures reached actual
HTTP 403 and503 discovery responses, started no adapter, and found zero
successors instead of one. Both cases failed before the production
change.
- Behavioral GREEN: both cases waited for the real retry deadline, used
the normal queue worker, ran the controlled adapter once after fresh
discovery, and read the original saved bytes. Two cases passed in 74.73
seconds. No external model or provider request was made.
- Deadline RED/GREEN: a combined Retry-After and quota-reset response
initially selected the earlier deadline. The regression now selects the
later deadline.
- Initial repair at `d8d4714a18` (same production source as
`018e8c52d345d79972d7907468080dc9908c299f`): seven affected files
passed, 69 cases in 185.16 seconds. That test-only cleanup then passed
all five real-database acceptance cases in 134.76 seconds.
- Commands: `pnpm exec vitest run` with
`heartbeat-repository-catalog-recovery.test.ts`,
`github-catalog-timing.test.ts`, `github-grant-metadata.test.ts`,
`project-repositories-persistence.test.ts`,
`task-workspace-repositories-persistence.test.ts`,
`github-repository-catalog-failure.test.ts`, and
`services/heartbeat/retries.test.ts` from the server source tree. The
acceptance file also ran fresh at `018e8c52d3`. A fresh five-case run
after the P1 repairs passed at `f490ca6b78` in 122.06 seconds. The final
eight-case file passed fresh at
`e5b2efab11919467850f55fd6345317132b9b672` in 124.62 seconds, using real
owned embedded PostgreSQL. It covers automatic recovery with saved
bytes, genuine denial, provider-started classification, normal Stop, the
direct HTTP route, and both partial-catalog outcomes.
- Review repairs at `f490ca6b787cf77df96d3d6edfb2ab9115eb19a6`: both
reported P1s reproduced RED. The real database interaction scheduler
chose a retry after 24 seconds before the provider deadline of one hour.
A secondary HTTP 403 with valid Retry-After lost its retry
classification. The narrow fixes then passed 44 cases across scheduler,
header, and metadata-contract files in 37.66 seconds.
- A real direct request through the task workspace router and normal
error middleware reproduced a 500 response instead of the existing 403.
Required-ID retry mode now applies only to admission and its rechecks.
The route and both two-connection partial-catalog cases then passed in
29.09 seconds. Both partial cases use actual owned grants and credential
bindings.
- All seven server type layers passed again at final head
`e5b2efab11919467850f55fd6345317132b9b672`. The affected test type layer
passed again after the test-only cleanup. Environment: macOS arm64, Node
26.4.0, pinned pnpm 9.15.4, Vitest 5.0.3, owned disposable PostgreSQL 18
on loopback. The current checkout's shared packages, plugin SDK, and
Runner were built first.
- One local acceptance run under concurrent owned test/typecheck load
hit the unchanged 15-second Stop-case timeout. Its four other cases
passed. The unchanged complete file then passed serially. The timeout
receipt remains recorded; no assertion or timeout was weakened.
- A provider-started failure retains the existing generic adapter retry
policy. It does not receive the catalog no-provider certificate or a
provider retry deadline. Fixture cleanup cancels its scheduled retry
through the normal owner.
- Final head `e5b2efab11919467850f55fd6345317132b9b672` has 59
successful checks, 3 allowed skips, no pending or failed check, and
Greptile 5/5 with no open findings. [Completed CI
workflow](https://github.com/paperclipai/paperclip/actions/runs/42322202977).
- Additional local diagnostic for the late recovery-owner advisory: on
unchanged `e5b2efab`, the dedicated scheduler returned `retry_exhausted`
with attempt 3/max 2. The normal stranded sweeper then escalated once,
created no extra run, and started no adapter, even after catalog access
recovered. One case passed in 17.32 seconds. The existing
failed-predecessor path delegates to that bounded scheduler and returns;
it does not enter a new generic retry budget. No production registry
change was made. This diagnostic was not a CI case. Its local harness is
preserved separately at `74e3854eea3f7c4121fdcd9c8a8ad522cd8c7477` on
`codex/catalog-exhaustion-acceptance`; its production source is
identical to `e5b2efab`.
- Module boundary and whitespace checks passed.

## Risks

- A temporary failure from another eligible connection can delay a truly
missing repository for the existing limit of two attempts. It cannot
grant access.
- Unknown network errors and genuine access denials retain their
existing behavior. This change handles known HTTP failures only.
- Historical provider quota causality is unproven. This repair has a
separate local producer regression. It does not rewrite past failures or
queued user intents.
- Acceptance uses real local database and heartbeat owners with
controlled HTTP and adapter fixtures. It does not claim a live
native-provider recovery.

## Model Used

OpenAI GPT-6.1 Codex (`gpt-6.1-sol`), with high reasoning, repository
tools, code execution, and controlled local PostgreSQL tests. The host
did not expose a context-window size.

## 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-11-06 19:29:40 -06:00
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
8f1cb11a03 fix(workspaces): name the owner action when a shared source is restricted (#16545)
A reused task workspace in a shared project checkout keeps its source
denial when another task's restricted worktree shares that checkout
(#16095 deliberately does not clone over existing workspaces). The run
then failed as an opaque `setup_failed` 403 and the task was blocked
with a generic "no live execution path" notice.

Convert that exact denial into a `configuration_incomplete` failure
with reason `workspace_source_restricted`, give the task notice and the
recovery action the owner step, and keep this pre-dispatch owner action
out of the unexpected Sentry run-failure stream. The reason and
predicate live in a dependency-free module. Access checks are unchanged.

Co-Authored-By: Paperclip <Paperclip-Paperclip@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 01:33:53 -07:00
Devin FoleyandPaperclip c7f4b74111 fix: isolate fresh task sources from restricted project workspaces (#16095)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Each task must have an isolated workspace that its agent can read.
> - Older project checkouts can contain worktrees for restricted tasks.
> - The source access check correctly rejects these shared checkouts.
> - A fresh task then fails even when its agent can clone the project
remote.
> - This pull request gives fresh default tasks a separate clone of the
same remote.
> - The task can start while the restricted task files remain protected.

## Linked Issues or Issue Description

**What happened?**

Fresh tasks fail with `Task workspace access is no longer available`
when their default project checkout contains a restricted task worktree.
The caller has access to the new task and its remote repository. The
shared local Git source is unsafe to copy.

**Expected behavior**

Use a separate clone of the same authorized remote for a fresh default
task. Keep source access checks and responsible-user Git credentials in
force.

**Steps to reproduce**

1. Put a restricted task worktree inside a project checkout.
2. Assign a new default task to an agent that cannot read the restricted
task.
3. Start the new task. The current source check fails before workspace
creation.
4. With this fix, the task uses a separate remote source. The restricted
files are absent. Later runs reuse the same owned source.

Related work: Refs #15749, Refs #15528. This change keeps their source
retention and workspace ownership checks.

## What Changed

- Add a remote source path scoped to the company and task.
- Clone through the run’s Git connection after the shared source fails
its access check.
- Reject reused workspaces, explicit selections, pinned branches, local
sources, credential-bearing URLs, and symlink paths.
- Check source access again before publishing the workspace.
- Resolve later runs from the verified recorded source. Ignore leftover
clones that the saved workspace does not own.
- Record provenance for new implicit Git defaults. Read the current task
binding before fallback. Preserve explicit, inherited and ambiguous
legacy checkout choices.
- Keep the effective task strategy during startup validation of matching
implicit defaults. Preserve the existing explicit-selection write check.
- Add regression coverage and document the fallback.

## Verification

- Merged master `c8efdbfba43` and resolved both rounds of catalog/index
conflicts. All upstream suites, classifications, source references and
story IDs are preserved. Both fix branches combine cleanly in either
merge order, with no catalog fingerprint drift.
- Passed locally: 313 focused source-isolation, preparation,
task-creation, binding and trust tests before the final startup fix. The
final fix passes all 100 source-trust, task-binding and
environment-selection tests. Earlier workspace-resolution and
persisted-source suites also passed.
- Real-Git regressions cover all three review findings. Reconstruction
ignores an unrecorded clone and preserves unpublished commits/files.
Explicit and parent-inherited checkout IDs without typed selections
retain their denial and branch tips without cloning or acquiring an
environment. A protected-project heartbeat completes and resumes using
the task’s Git strategy with read access. Explicit selection still
requires writes, and a mismatched default is rejected.
- Passed: the complete Storybook catalog check with no drift errors,
feature and agent map structure checks, and all 17 catalog/index script
tests. The generated index counts match the merged catalog. The current
upstream verification-route inventory remains unchanged. The catalog
contains all 262 authored suites and 2,481 story IDs.
- Passed: full workspace typecheck and build after the final guard
changes, on latest head `79d2adadcb6d3d7a04b9943a91bdb8b0b6abea6d`.
- Latest head `79d2adadcb6d3d7a04b9943a91bdb8b0b6abea6d`: complete CI
passed (54 successful checks, two intentional skips), including the full
general/serialized suites and all eight browser shards. Fresh Greptile
Apex review is 5/5 on this same commit, with no unresolved findings or
recommendations. The full local test command was attempted earlier
without a complete aggregate result; the complete suite result is from
CI.
- Manual check: configure a shared project checkout with a restricted
task worktree. Admit a fresh default task using the authorized remote.
Confirm separate Git storage and no restricted local branch or file.
Reuse, close and reopen the task workspace. Confirm that explicit,
literal branch and low-trust selections retain the denied source.

## Risks

- A clean remote clone excludes unpublished local work. The fallback
applies only to fresh default tasks and rejects existing workspace or
branch selections.
- A private remote can fail to clone when the responsible user lacks Git
credentials. The run then reports the failure; access checks are not
bypassed.
- Older untyped checkout bindings cannot prove default provenance. They
keep the source denial instead of silently dropping unpublished work.
- New implicit Git defaults use the existing workspace-intent contract.
The source directory uses existing instance storage. No schema migration
is required.

## Model Used

OpenAI GPT-6, model ID `gpt-6`, through Codex. Used reasoning,
repository inspection, code editing, terminal tools, and test execution.
The API session does not expose an exact context window size.

## 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-13 12:31:40 -07:00
DottaandPaperclip 8f1905d34d fix: provision all project repositories for local and sandbox tasks (#13442)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Projects can now attach several source repositories.
> - Task preparation still treated these sources as alternative
workspaces.
> - Sandbox sync preserved Git history only for the selected repository.
> - A task needs every attached repository to complete work across the
project.
> - This pull request prepares all distinct project repositories and
preserves their separate Git histories through sandbox restore.

## Linked Issues or Issue Description

**What happened?**

A user reported that a project with two repositories received only the
first repository in Daytona. Repository-only project rows also reached
the agent with null local paths. Managed checkouts with matching
repository names could resolve to the same directory.

**Expected behavior**

Local and sandbox tasks receive every distinct repository attached to
their project. Repository-only sources work without preconfigured local
folders. Each repository keeps its own Git history and working files.

**Steps to reproduce**

1. Create a project with two repository sources and no local folder
paths.
2. Assign a task to the project and run it in Daytona.
3. Inspect the task workspace and the repository paths exposed to the
agent.
4. Observe that the original implementation supplies only the selected
checkout.

Related change: #13010 added multiple repository selection. The open
repository-catalog proposals #11234 and #11228 cover a different data
model. This fix uses the existing project workspaces.

## What Changed

- Materialize each additional distinct repository as an editable
checkout inside the task root. Seed configured local sources with their
current working files and retain task edits across runs.
- Pass materialized repository paths to local agents and native sandbox
task prompts. Apply existing run-scoped Git credentials to each remote
clone.
- Preserve each repository's Git history, dirty files, and restore
baseline during sandbox staging and durable recovery. Apply each
repository's ignore rules and the operator's workspace exclusions.
- Keep same-name managed repositories in separate directories. Report
additional clone failures before the task starts.
- Add task-level, checkout, sandbox round-trip, environment-hint, and
recovery-descriptor regression coverage. Document checkout and restore
behavior.

## Verification

- Red: the original implementation fails the sandbox test because the
second repository has no Git directory. It also fails the same-name
checkout test and both real-database task tests because repository hints
have no local path.
- Green: focused tests pass for one and two repository-only sources,
local source edits, clone failures, per-repository credentials, separate
Git histories, ignored files, and recovery from remote or durable seed
state.
- Live Daytona smoke passed with two disposable repositories through the
production provider sync functions. Both repositories arrived with Git
history. Commits from both restored locally. Ignored files stayed
excluded. The disposable sandbox was deleted.
- Passed on final commit `93ab76763`: `pnpm -r typecheck` and `pnpm
build`.
- Final focused coverage: 254 assertions across the six changed test
areas passed across the serial run and an isolated rerun of the existing
process-kill timing test. The live Daytona smoke also passed.
- The local `pnpm test:run` overlapped source edits and retained stale
transformed code. Its first phase reported 12,240 passed assertions,
nine failed assertions, three hook failures, and one worker error; later
phases did not run locally. This run is not claimed as green. Fresh
focused tests verify the changes, and every general/workspace and
serialized-server CI shard passes on the final commit.
- Final CI is green on `93ab76763`: all test shards, all three browser
shards, typecheck, build, runner verification, canary dry run, and
security checks. The initial unrelated chat-delivery browser timing
failure passed in the final CI run. Optional Storybook visual checks
were skipped.
- Greptile is 5/5 on the final commit with no unresolved review threads.
Its checkout-race finding was reproduced with a failing test, fixed, and
rechecked.

## Risks

- Additional repositories need disk space and clone time. Access failure
for an attached repository stops preparation.
- Additional checkouts live under `.paperclip-repositories/` and keep
independent histories. Changes stay in those task copies; they do not
overwrite configured source folders.
- Detached or reconfigured repository copies are retained under
`.paperclip-runtime/detached-repositories/`. Sandbox recovery retains
per-repository merge baselines.
- No database migration, UI contract change, or new credential
delegation is required. Referenced projects retain their separate
read-only behavior.

## Model Used

OpenAI Codex, based on GPT-6, with repository inspection, code
execution, and tool use. The runtime does not expose a more specific
model deployment ID or context-window size.

## 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-09-14 18:37:51 -05:00
DottaandPaperclip b97101893f feat(projects): select multiple GitHub source repositories (#13010)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Projects give tasks a common source repository and execution
context.
> - The current project form asks for a raw URL and unrelated metadata.
> - Teams need to select several repos from GitHub connections they can
use.
> - This pull request implements the reviewed project form and
repository editor.
> - The server checks credential ownership and shared audiences before
discovery.
> - Existing workspace URLs and runtime identity rules remain
compatible.

## Linked Issues or Issue Description

**Problem or motivation**

Project creation accepts one raw repository URL. It does not help users
select repos from their usable GitHub connections or attach several
repos together.

**Proposed solution**

Add a shared GitHub repository picker to project creation and
Configuration. Support multiple selections, transactional persistence,
and the existing GitHub setup flow. Simplify the project form and
Configuration tab as reviewed.

**Alternatives considered**

Keep a raw URL field or add a separate repository table. The existing
workspace collection already supports several repositories and keeps
legacy URLs compatible.

**Roadmap alignment**

This builds on the shipped MCP Tool Gateway and Apps capability. It does
not change runtime credential delegation.

Related work: #11662 addresses the existing dialog's viewport limits.
#4552 addresses generic Git URLs; this change preserves those URLs in
existing workspaces.

## What Changed

- Add company-scoped repository discovery from usable personal and
shared GitHub grants, with provider-ID deduplication, PAT pagination,
and partial failure handling.
- Document the repository endpoints and board access requirements in
OpenAPI.
- Validate new selections and save projects with multiple repository
workspaces in one transaction. Preserve legacy URLs and existing
selections whose access was lost.
- Implement the reviewed Create project dialog, shared repository
editor, scrolling, and mobile layout.
- Move repositories above environment variables, remove Status and Goals
controls and env help paragraphs, move Created to the bottom, and
redirect Overview to Configuration.
- Reuse GitHub setup in dialogs, preserve project drafts, and verify
popup completion through the API.
- Replace the configuration story's DOM adapter with explicit production
composition. Keep the reviewed mobile and short-viewport stories.

## Verification

- Passed: `pnpm build`, `pnpm -r typecheck`, `pnpm build-storybook`, and
`pnpm check:token-gates`.
- Passed: focused repository access, database persistence,
configuration, and connection setup tests.
- Passed: `pnpm exec playwright test --config
tests/e2e/playwright.config.ts tests/e2e/project-repositories.spec.ts`.
- The browser tests use a real temporary server/database. They cover
create, forty persisted repos, mobile scrolling, save/reload, legacy URL
editing, and rejection without a partial project.
- GitHub responses and popup completion use deterministic fixtures. No
real GitHub account was authorized by the test suite.
- All CI general, serialized server, and browser test shards pass on the
final commit.
- The local full-suite run overlapped review edits and was stopped;
fresh repository, OpenAPI, UI/CLI, and connection tests pass. Unrelated
local worker, built-in-agent, and routine timing/socket failures passed
isolated reruns.
- Final commit `1b3308dca`: all CI gates pass, including build, runner
verification, typecheck, canary dry run, and security checks. Greptile
is 5/5 with no unresolved review threads.
- Storybook visual regression is opt-in and was skipped by CI; the
Storybook build passed locally.

## Risks

- Repository discovery depends on provider availability. Failed
connections are reported while successful results stay usable.
- Selections identify source workspaces; they do not grant agents new
credentials. The existing primary-workspace and responsible-user
identity rules still apply.
- No database migration is needed. Existing API status, goals, dates,
and manual workspace URLs remain supported.

## Model Used

OpenAI Codex, based on GPT-6, with repository inspection, code
execution, and browser tools. The runtime does not expose a more
specific model deployment ID or context-window size.

## 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-09-08 08:21:28 -05:00