Files
PaperClipAI/doc/connections/GITHUB.md
T
b929ad43de fix: keep dependency waits neutral and explain GitHub access failures (#19315)
## Thinking Path

> - Paperclip manages agent work and shows users what can move it
forward.
> - Task status and tool approval cards describe different parts of that
work.
> - A queued dependency wait has no provider attempt to retry.
> - GitHub can reject an approved action while other connection tools
still work.
> - These cases showed a false Retry banner and an unhelpful provider
error.
> - This pull request keeps dependency waits neutral and gives GitHub
access failures a direct repair path.

## Linked Issues or Issue Description

Related: #19268 preserves pending Retry preparation and current failure
causes.

**What happened?**

A queued wake cancelled by the dependency gate appeared as a failed
attempt with Retry. A GitHub MCP permission denial appeared as raw HTTP
error text after an approved repository action.

**Expected behavior**

Keep the task's dependency state. Show GitHub access failures with a
clear explanation and a link to the selected connection's Permissions
page. Preserve actual failures, provider details, and approval gates.

**Steps to reproduce**

1. Queue a wake, then leave its task dependent on unfinished work before
dispatch.
2. Open the task after the dependency gate cancels that wake.
3. Separately approve a GitHub action whose MCP result contains `403
Resource not accessible by integration`.
4. Reopen the saved tool approval.

## What Changed

- Recognize both queues' unstarted dependency-wait receipts in
cancellation provenance and task projections. No dispatch or recovery
gate changes.
- Classify GitHub's specific permission error after MCP result
normalization. Keep result validation, the raw diagnostic, and working
tools available.
- Apply the same display code to both historical GitHub error codes.
Identify the provider from company-scoped connection metadata, including
renamed apps, without rewriting stored decisions.
- Show a token-based inline access card with **Manage GitHub access**
and collapsed **Error details**.
- Give agents trusted recovery steps after an approved GitHub action
fails. Keep provider text in the untrusted result and prevent automatic
replay.
- Add regressions, a production-component Storybook case, and
execution/connector documentation.

## Verification

- Red before the fix: cancellation provenance, dependency-wait
projection, the real queue cancellation, approved MCP failure
classification, saved interaction projection, and the error card
assertions failed at the expected behavior.
- Green focused tests: queue cancellation, one ready-task executor,
approval-before-dispatch, one failed GitHub request, connection reuse,
retained provider diagnostics, saved interaction reads, task list/detail
projection and redaction.
- UI: 106 component tests passed. Token gates and Storybook catalog
checks passed.
- `node scripts/typecheck-low-memory.mjs`: passed.
- Local source instance: verified a saved dependency wait stays neutral
after reload; a saved GitHub error shows repair guidance, expands its
original error, and opens the correct connection Permissions page.
PostgreSQL and HTTP APIs were real. The GitHub denial was a controlled
MCP fixture; no account or provider permission changed.
- `pnpm build`: passed for all workspace packages, the Runner binary,
server, CLI and UI. The server build and all server typecheck layers
also passed after the review repair.
- Additional safety checks passed for realistic repository arguments,
active recovery, failed Retry preparation, and bounded provider
diagnostics.
- Review repair: 45 pure tests and 7 PostgreSQL cases passed. These
cover both historical error codes, renamed apps, and
company/provider/application identity boundaries. A second local browser
check verified the renamed app, retained error detail, and correct
Permissions destination.
- Final review regressions: the dispatcher and metadata cases failed
before the repair. Afterward, 25 focused cases and 59
projection/identity cases passed, including connection-key precedence.
The native wait and app-only GitHub source also passed a local browser
check.
- The historical read adds at most one connection query per interaction
read batch. It selects IDs and the provider key only and runs only for
candidate permission failures. It adds no provider requests.
- Full local `pnpm test:run` stopped at the chat suite's `afterAll`
cleanup timeout. All 268 assertions in that group and the three prior
chat groups passed. The failure is retained here; it is not counted as a
full-suite pass. CI passed on `e8a692e1e2` after one workspace-startup
rerun. Fresh CI is pending for the combined head. Its first workspace-a
attempt timed out in the CLI company import/export test's child-server
startup wait; 4,968 UI tests and 287 CLI tests passed in that job. The
same shard passed on the preceding commit. The workspace shard passed on
its one rerun. All other test, build and browser jobs passed. The [first
failure](https://github.com/paperclipai/paperclip/actions/runs/42093823928/job/127478869941)
is retained in the Actions history. Greptile reviewed the preceding head
at 5/5. Fresh review is pending for the combined head.

## Risks

The GitHub classifier matches the documented permission-denial phrase,
not every 403. Rate limits, missing resources and other providers retain
their existing error behavior. Historical normalization affects display
only. A dependency cancellation needs an unstarted run and a supported
queue receipt; current user gates, Retry preparation and recovery remain
authoritative. No schema migration or credential changes.

## Model Used

OpenAI GPT-6, through the Codex agent. The exact runtime model ID and
context-window size are not exposed in this session. Used reasoning,
repository tools, TypeScript, PostgreSQL, MCP fixtures and browser
verification. The parallel agent-recovery commit records Anthropic
Claude Opus 5.5 as co-author.

## 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 described the issue in-PR using the bug template
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change
- [x] I have run the focused tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation
- [x] I have considered and documented risks
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open findings
- [x] I will address reviewer comments before requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 20:55:34 +00:00

405 lines
23 KiB
Markdown

# GitHub managed connection
GitHub is a Paperclip Cloud-managed GitHub App connection with an advanced PAT
compatibility method. Cloud owns the fixed public OAuth callback and signed
webhook inbox; provider tokens are sealed to the enrolled instance and stored
only in its existing encrypted secret system.
## Catalog entries
**GitHub** connects an account for repository tools, Git, and `gh`, and opens
Access → Connect directly. **GitHub Code Review Bot** connects one agent to a
GitHub App for pull-request reviews and mentions, and opens Choose agent directly.
The bot entry follows **Settings → Experimental → GitHub review bots**
(`enableGitHubReviewBots`), which defaults to off. It is independent of Chat
connectors: enabling one does not enable the other. Turning off the GitHub
setting hides setup and management; existing bots keep running.
Both entries reuse the existing GitHub integrations. Bot endpoints retain the
`github` provider identity and existing setup, reconnect, and management URLs;
saved bot connections and drafts appear under GitHub Code Review Bot. GitHub
repository and MCP URLs still resolve to the ordinary GitHub tool connection.
## Bot instructions and triggers
Bot Settings uses the shared Markdown editor for common and event-specific
instructions. Type `/` and select a company skill to insert a saved skill link.
Common instructions accompany every admitted GitHub task; event-specific
instructions accompany that event. Selected skills are materialized for the run
and explicitly invoked by the native Runner, subject to existing tool and
isolation restrictions. The admitted configuration snapshot determines skill
selection for each wake, including queued events. GitHub messages, repository
content and other companies' skills cannot assign skills to the run.
Authorized @mentions remain available when **Run automatically** is on or off.
The switch in Settings reveals the PR and issue event checkboxes. Access lists
company members and external contributors separately. Author include/exclude filters
in Settings control whose activity starts automatic work; there is no per-person
automatic-run switch. Automatic events still require an authorized author and an
enabled event. Legacy `people[].automaticReviews` values are accepted but ignored.
External contributors additionally need **Allow automatic runs for external contributors**.
Their restricted guest profile and sponsor requirements are unchanged.
The passing-score number field accepts a whole number from 1 to 5, defaulting to 5.
Paperclip publishes a successful **Paperclip Review** check only after a complete
assessment of the current commit meets the threshold; lower scores fail and
incomplete assessments require action. To block merging, separately require this
check from the dedicated App in GitHub branch protection or a ruleset. A new head
commit needs its own check. Report-only mode publishes a neutral conclusion, which
GitHub can accept for a required check; it does not enforce a score requirement.
This includes incomplete assessments and failed runs: their checks remain neutral
and explain the failure without presenting a passing assessment.
A passing score does not formally approve a PR. **Approvals and change requests**
contains those optional agent permissions. Scheduling filters, ignored files,
and inline-comment options have separate disclosures. The header shows the App
identity, copyable mention, avatar download, and GitHub branding settings.
Published review summaries use the GitHub bot's display name in their heading.
The check name remains **Paperclip Review**. Multiple bots can review the same
PR through separate GitHub Apps: each keeps its own task, summary, findings, and
check. Check updates match both the App ID and the endpoint/PR/commit identity.
Repository branch rules decide separately which App's check is required to merge.
## Self-hosted setup
The setup screen states the default access in one line, with **Change** for
other choices. **Continue to GitHub** on that screen starts the provider
handoff.
A self-hosted instance needs one Paperclip Cloud approval before its first
managed connection. After approval, setup returns to the connect screen and continues to
GitHub without another instance approval or a service restart.
If an unapproved enrollment link expires, return to setup and select
**Continue**. Paperclip asks the server for a valid link. The server reuses a
live pending enrollment or replaces an expired one; this does not revoke or
repeat an existing instance approval.
## Identity resolution
Fresh setup does not reuse an archived personal connection found by name and
provider. It creates a separate application and connection with an available
name, such as `GitHub (2)`. The archived identity, credentials, grants, and
history stay unchanged. An explicit reconnect or application-targeted setup
continues the existing identity and requires its personal owner.
Every MCP call, `gh` invocation, native Git operation, checkout, health check,
and webhook binding uses the same order:
1. An active dedicated GitHub grant for the current agent.
2. The active personal GitHub grant owned by the run's `responsibleUserId`.
3. For automated work without a responsible user, a personal grant only when
an existing standing delegation names the agent.
4. Legacy `GH_TOKEN`/`GITHUB_TOKEN` only when no managed GitHub connection is
configured for the company.
An unavailable or ambiguous managed identity fails visibly. It never falls
through to another person, an organization credential, or a legacy token.
Agent grants are company-scoped, have exactly one `subjectAgentId`, cannot be
organization defaults, and are installed only for that agent.
The connection installation is the credential owner's consent boundary. A
personal setup may target every agent or a selected set, and runtime resolution
considers only an enabled, active connection installed for the current agent.
Within that boundary, Paperclip treats the run's server-resolved
`responsibleUserId` as its credential principal, including for automated work;
agents cannot choose or spoof this field. The owner must still be an active
non-viewer company member at each use. A standing delegation is needed only
when a run genuinely has no responsible user.
## Credential lifecycle
The production, staging, and development GitHub Apps deliberately disable
user-to-server token expiration. The resulting long-lived access token is
checked with GitHub's `/user` endpoint every 30 days, together with installation
and repository summary refresh. Routine continuity requires no browser visit.
If GitHub returns an expiring access token and rotating refresh token instead,
Paperclip stores both encrypted and:
- refreshes at least one hour before access expiry;
- forces a rotation at least every 30 days while the instance is active;
- serializes refresh through the existing database refresh lease and compare-
and-swap update;
- atomically advances both secret values before clearing the lease;
- retries one forced refresh after a provider `401`.
Only an unrecoverable provider invalidation marks a grant
`needs_reauthorization`. Installation removal or suspension is reported as an
installation-health failure, not as token expiry.
## Repository access
GitHub can return `403 Resource not accessible by integration` inside an MCP tool
error even when the connection itself is healthy. A failed tool approval shows
**GitHub access needed** and **Manage GitHub access**, which opens that connection's
Permissions page. Check the connected account and the App's access to the intended
account and repository there. The original provider error remains in the action's
error details and diagnostic record. Paperclip does not replay the write or hide
other working tools. Saved GitHub approval errors receive the same presentation.
The agent's continuation wake carries server-written next steps and tells it
not to retry until access is repaired. It must never automatically replay a
stored approval.
Deferred historical errors receive the same guidance as their saved cards.
For an App connection's `create_repository`, the agent
asks the user to create the repository on GitHub, give the App access, and select
**Refresh access**, then continues with that repository. Token connections ask
the user to check token permissions and repository selection, then update or
replace that token. If the method is unavailable, the agent asks the user to
check the connection's authentication method. GitHub's text stays in the
untrusted result.
OAuth completion verifies `/user`, every page of `/user/installations`, and every
page of each installation's accessible repositories. Setup remains incomplete
until at least one installation and repository are available. Paperclip stores
the authenticated username and a grant-scoped display snapshot containing only
repository IDs, full names, installation IDs, and private-repository flags. GitHub stays authoritative:
this snapshot never authorizes repository access.
The permissions page shows repositories across authorized accounts by default.
Use the account filter and search to narrow the list. The list scrolls after
about ten rows and marks known private repositories with a lock. Configure on
GitHub opens the app account chooser so users can add or update organization
access. Refresh access after changing the selection. Older snapshots omit the
private flag until refreshed. If a legacy grant lacks its app chooser URL,
**Load GitHub configuration** refreshes access and recovers the app slug from
GitHub installation metadata. The page does not substitute a single-installation
settings URL for the account chooser.
The permissions page shows the authenticated GitHub account and the complete
accessible repository list. **Refresh access** reloads it from GitHub. Older
grants and grants invalidated by newer installation lifecycle events prompt for a
refresh instead of presenting a stale list. The page links to GitHub's
installation management page. Selected repositories are recommended; all-
repository access retains its warning.
Fresh local test-drives use production Paperclip Cloud. Instance enrollment
and provider enablement are separate: enrollment alone does not enable GitHub
OAuth. Production must advertise the `github.code` profile (see Cloud's
`docs/github-connector-deploy-bootstrap.md`). If it is unavailable, setup
preserves the sign-in intent and offers a retry instead of silently switching
to a personal access token. A successful retry preserves the chosen audience.
## GitHub Actions tools
Managed and PAT connections request `X-MCP-Toolsets: default,actions` for MCP
discovery and invocation. GitHub's default catalog excludes Actions; granting
Actions permissions alone does not expose workflow tools. Existing connections
can use **Refresh actions** after upgrading to discover the added tools.
The normal catalog, access, approval, and quarantine rules still apply.
To dispatch an existing workflow, use `actions_run_trigger` with
`method: "run_workflow"`, the repository owner and name, `workflow_id`, `ref`,
and any workflow `inputs`. The workflow must declare `workflow_dispatch`.
The GitHub App installation or fine-grained PAT needs **Actions: Read and
write** for the repository. App owners set that permission on the GitHub App
registration; installation owners must approve an increase before it takes
effect. Paperclip's action controls do not grant GitHub permissions.
The tool also supports rerunning and cancelling runs and deleting run logs.
It retains GitHub's destructive classification. Read tools include
`actions_list`, `actions_get`, and `get_job_logs`.
Provider references: [MCP toolset configuration](https://github.com/github/github-mcp-server/blob/main/docs/server-configuration.md)
and [workflow dispatch permissions](https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event).
GitHub response reconciliation reads run status and request identity without
loading prompts, runtime profiles, results, or log excerpts. Request ownership
checks still use current company, agent, task, and accepted message provenance.
The run context projection retains `issueId`, `taskId`, `wakeCommentId`, and
`wakeCommentIds`; larger runtime data stays in its owning reads.
## Webhook delivery recovery
The Cloud connector authenticates the outer event batch before it processes
individual encrypted App deliveries. If an inner delivery cannot be decrypted
or fails its route binding check, the instance reports a failed delivery and
leaves it unacknowledged in Cloud. A local warning includes the authenticated
delivery ID and a fixed failure reason, without event contents or raw errors.
Other valid events in the batch can still finish and acknowledge independently.
Failed ciphertext never reaches the
GitHub webhook handler. The broker retains failed deliveries for retry under
its normal lease and retention policy; this does not recover a lost old key.
## Webhooks
For the shared GitHub account connector, Paperclip Cloud verifies
`X-Hub-Signature-256` against the exact bounded request
body before parsing, deduplicates by `X-GitHub-Delivery`, and persists a minimal
normalized event before returning `202`. Raw webhook payloads are discarded.
When registering an active binding, Paperclip sends the current user token only
inside the signed, payload-bound broker request so Cloud can verify access to
that exact installation; Cloud neither logs nor persists that proof token.
Deliveries fan out independently to every enrolled instance bound to the GitHub
installation and are sealed to each instance's public key.
The instance polls with backoff, stores a company-scoped idempotency receipt,
and acknowledges only successful applications. A merged pull request updates
its matching external-object snapshot and immediately runs the existing merge-
confirmation resolver. It wakes the assignee only when that interaction's
continuation policy requests it; unrelated Paperclip issues are not closed.
The periodic GitHub merge sweep remains the reconciliation fallback.
Installation lifecycle events refresh or invalidate installation summaries and
remove obsolete Cloud bindings. Activity records contain event identifiers and
outcomes but no webhook content. GitHub webhook content is never first-party
telemetry.
## Dedicated agent Apps
The bot wizard uses Cloud gateway protocol version 2. The stack builds the
private manifest for a personal account or organization. GitHub still asks the
user to confirm creation and repository installation. The stack exchanges the
returned manifest code directly with GitHub and stores every App secret in its
vault, including the private key, client secret, and webhook verifier.
Cloud stores only opaque, instance-bound callback routes and encrypted callback
claims. It does not exchange codes, track App or installation identity, store
GitHub secrets, or interpret bot events. It forwards original webhook bytes,
provider headers, and signature in an envelope sealed to the stack before
writing the inbox. The stack decrypts and authenticates those bytes through its
existing durable ingress. Cloud-managed Apps are excluded from the direct-tenant
callback recovery scanner and its callback-mismatch warning; the Cloud inbox owns
relay delivery retries. Only that local check can verify a connection or
admit work. After a repair, a fresh authenticated event for the bound installation
can verify webhook delivery even when the relay deduplicates a redelivered old ping.
This also applies while the connection needs attention: the signed event verifies
delivery without retaining its content, admitting work, or changing repository
access, even when its repository is disabled or unavailable. Verification returns
the connection to the provider-setup step; later installation events cannot
activate it. The manager must still complete reconnect and activation before new
review requests can run.
Shared-App account connectors keep the behavior described above.
Unverified gateway traffic has request, byte, pending-queue, retention and wake
limits. Its transport deduplication includes the body and signature, so a forged
request cannot occupy a real delivery's identifier. A ping received before its
verifier reaches the vault remains queued for retry. Malformed and forged
traffic is discarded after local verification. Installation returns only request
a state refresh; the stack queries GitHub to establish installation authority.
OAuth state, PKCE, consent, repository discovery, recovery and lifecycle changes
also stay in the stack. Interrupted single-use manifest exchanges require
recovery of the existing App instead of a second creation. Direct webhook and
manual existing-App recovery remain available. Connecting does not prove that
an agent runtime can execute a review.
### One main response per pull request
A pull request task can retain both its main discussion and inline finding
threads. Bot tool discovery lists each endpoint/catalog tool once across those
threads. Publishing an inline finding therefore does not duplicate the tool
catalog on the task's next run. Both conversation mappings remain available for
authorized follow-up replies.
The main PR conversation shares one App-owned comment across pushes, mentions,
and repeat reviews. A new request updates that comment to **Review queued…**;
agent progress and the final assessment update the same comment. Its identity
survives restarts and reconnects. Existing request comments can be adopted without
creating another main comment. Issue conversations and inline-review threads
retain one editable response per request. Description mentions and automatic
tasks also receive an acknowledgement. No new eyes
reaction is added; cleanup still handles receipts created by older versions.
The agent's `update_comment` tool edits the current request's working comment.
It takes a body and a stable idempotency key, with no caller-selected comment ID
or destination. Instructions encourage brief, factual progress updates during
longer work. Distinct updates use distinct keys; retries reuse the same key.
Progress does not complete an assessment or change a review check.
The `comment` tool replaces that same comment with the final answer.
`submit_review` replaces it with the allowed review summary; checks, inline
findings and explicitly permitted formal reviews remain separate. Corrected
assessments can update the summary again. Delayed progress cannot overwrite a
final answer. Native final text stays in Paperclip and creates no extra comment.
Legacy connections without a saved review configuration retain automatic run
progress and final replies until upgraded to this tool-owned response model.
Receipts are bound to the company, App, task, accepted request and original
runtime generation. The existing publication lease serializes edits, and
App-owned markers recover uncertain creation without posting duplicates.
Deleted or no-longer-owned comments are not recreated or edited. Coalesced runs
settle a deleted earlier working comment without blocking the current response,
review findings, or check publication. Failed runs
update the same comment when no final reply is confirmed or unresolved. Maintenance
also reconciles acknowledged requests when recovery stops before a publication
is staged. Requests saved on a blocked task report that work is paused and stay
open for resumed progress or stopped-run cleanup. An older
run cannot replace a newer run's response. A run
that ends without a final reply clears a remaining working state honestly.
Repository restrictions, person authorization and governed tool checks remain
in effect for edits. Existing disabled or quarantined tools stay disabled.
Existing native sessions refresh incompatible tool checkpoints so the agent
can see `update_comment`. The same Paperclip task and saved history remain.
### Explicit bot mentions and subscriptions
Issue and PR descriptions, discussion comments, inline comments, and review
summaries retain uploaded attachment references when they mention the bot.
Private images use the exact unchanged source's authenticated GitHub rendering;
repository/thread identity and body hashes remain bound across restarts. Signed
image URLs and credentials are never added to durable attachment descriptors.
A manual message beginning with another recipient's @mention does not wake this
bot through its thread subscription, including Apps outside Paperclip such as
`@greptile review`. Mentions of other connected GitHub bots are also filtered
elsewhere in the body. Paperclip filters that
delivery before creating task work or adding an acknowledgement. Explicitly
mentioning both bots allows both to receive the request, subject to their normal
authorization and repository checks.
Unaddressed follow-ups and incidental human or unknown mentions retain the existing
subscription behavior. Explicitly mentioning this bot admits a request even when
another recipient is named. The routing rule does not change automatic issue or PR
event policies. Archived bots and identities from other companies or providers
are excluded from the routing lookup.
## Retrying a saved request
A normal task Retry can continue a newer saved GitHub message after an earlier
attempt failed. Execution revalidates the exact admitted successor, its current
linked principal and repository access, and the durable continuation receipt;
it does not repeat the admission that already consumed the recovery hold.
A failed native predecessor still requires its committed failed result and
exact executor termination proof. Successful results and uncertain delivery do
not grant a replay. A certified failed-chat Retry retains the exact accepted
message identity when its predecessor stopped before identity initialization,
so a published review can expose its authorized passive response wait.
If an inherited task-resume attempt already recorded a result, ordinary Retry
explains that exact replay is unavailable and directs the user to send a new
message to continue from saved work. That new turn still passes normal current
requester, access and executor-containment checks.
If that successor failed at Retry authorization before runtime or provider
startup, the next normal Retry follows its canonical source receipt. The exact
system diagnostic, settlement events, empty execution evidence and absence of
allocated resources establish that narrow recovery path. A generic setup
failure or a claimed `providerWorkStarted: false` does not establish it.
The canonical task Retry also recognizes an isolated-directory binding failure
at the workspace preparation boundary before allocation. Its structured reason
and task binding, trusted failed settlement, dispatch-only profile and absence
of execution resources must all agree. Other workspace validation failures do
not inherit this permission. Current request authorization still runs at both
admission and execution; a failed unadmitted Retry intent does not replace the
saved request or prevent a later deliberate Retry.
## Run projection
The resolved token is leased at run start as an audited class-3 secret and is
projected only into the child process:
- `GH_TOKEN`, `GITHUB_TOKEN`, and an internal credential-helper environment key;
- `GIT_TERMINAL_PROMPT=0`;
- process-scoped `GIT_CONFIG_COUNT/KEY_n/VALUE_n` entries that clear ambient
helpers, install a `github.com`-only helper, and rewrite GitHub SSH remotes to
HTTPS;
- author and committer identity using
`<numeric-id>+<login>@users.noreply.github.com`.
Tokens never appear in arguments, URLs, files, logs, events, or model context,
and the projection never replaces `HOME`.
Cloud deployment and exact GitHub App registration settings live in
`paperclip-cloud/docs/github-connector-deploy-bootstrap.md`.