Repository navigation
Conversation
…sitory Task creation took the team's first GitHub installation, while the connected-repository check read only one installation's repositories. A team with several installations could create a task whose installation cannot reach its repository, and the run then failed at clone time. Both paths now share one rule: the usable team installations in one order. Task creation attaches the first installation whose cached repositories include every private repository of the task, and fails at creation when none does. The connected-repository list covers every usable installation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
…creation The tasks API create path and the AI create-task tool still attached the team's first GitHub installation. The connected-repository list now counts every installation, so a repository only a later installation reaches passed that check and then ran with credentials that could not reach it. Both paths now take the installation that reaches the task's repositories and fall back to the first installation as before. The model tests also pin that one installation must reach every repository of a multi-repository task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion The repository cache refreshes only when a caller reads it with refresh allowed, so a cache synced before a repository existed made task creation fail for a repository the installation reaches. Creation now re-syncs the synced installations once when no cache confirms the repository, and treats an installation whose sync fails as unknown. Bot-authored tasks again fall back to the creator's personal GitHub only when the team has no installation, and the "can access" error appears only when a working installation exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An installation whose synced cache was empty was left out of the re-sync on a cache miss. A team whose only installation gained its first repository after that sync then failed task creation, where it used to attach the installation. The miss path now re-syncs every working installation, and task creation reads its installations through the same shared query. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion pick A re-sync that finds an installation uninstalled marks it gone, but the pick still attached it as unknown, so the task was created and then failed at run time. The pick now skips it, and task creation checks for a working installation after the pick. Public sandbox repositories need no credential, so the shared pick now ignores them for every caller instead of syncing every installation for them. The AI create-task tool resolves its installation before its transaction, so a GitHub sync never holds the transaction open. The raising tests age their caches so the mocked GitHub listing runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Risk: No findings This change makes task creation pick the GitHub installation that actually reaches the task's private repositories (cache first, one throttled GitHub re-sync to confirm a miss), and fails creation closed with "No GitHub installation of team … can access …" when none does; the repo list now covers every usable installation. I traced every credential-selection path (models, facade API, max tool, repo-selection agent) and found no security regression: all queries stay team-scoped, the bot-mode and error-path changes are fail-closed, and the unsynced/unconfirmed fallbacks attach same-team credentials only, matching prior behavior. Sentinel reviewed |
| for integration in integrations: | ||
| if wanted <= _cached_full_names(integration): | ||
| return integration | ||
| unsynced = next((i for i in integrations if i.repository_cache_updated_at is None), None) |
There was a problem hiding this comment.
we need to check the candidates that could own the repo before taking this fallback path, e.g. stale cache
| } | ||
|
|
||
|
|
||
| def _refreshed_full_names(integration: Integration) -> set[str] | None: |
There was a problem hiding this comment.
skipped refresh should leave access not confirmed orr trigger a targeted check rather than justify rejecting the task (and here we check against 60secs thresholds)
| return unconfirmed | ||
|
|
||
|
|
||
| def _still_working(integration: Integration) -> bool: |
There was a problem hiding this comment.
we need to handle transient failures from upstream gh
| } | ||
|
|
||
|
|
||
| def _refreshed_full_names(integration: Integration) -> set[str] | None: |
There was a problem hiding this comment.
can we honor retry-after on failed refreshes?
| .exclude(config__has_key=INSTALLATION_UNAVAILABLE_SINCE_CONFIG_KEY) | ||
| .first() | ||
| ) | ||
| if github_resolution_allowed and private_repositories: |
There was a problem hiding this comment.
can we move this outside the transaction, this blocks unrelated ops
Problem
agent.py,list_team_connected_repositories).Task._build_task). The tasks API and the AI create-task tool do the same.create_and_run_taskcaller that passes a repository is affected. Two examples: "Run with PostHog Code" in feat(workflows): request a change to a code-managed email template through PostHog Code #119032 and the experiment cleanup PR.Warning
🟡 Merge risk: medium · two-way door (merge-risk rubric)
create_and_run_taskfails at creation, including for a team with one installation.Changes
create_and_run_taskfails at creation when GitHub confirms that no installation reaches the repository. The error is "No GitHub installation of team … can access …". A team without a working installation still gets "does not have a GitHub integration".usable_team_github_integrations, lives in the lightgithub_repository_access.py.resolve_team_github_integrationuses it with no change in behavior.How did you test this code?
Test rationale:
Task.create_and_run(behindcreate_and_run_task), thePOST /api/projects/:id/tasks/endpoint,CreateTaskTool, andlist_team_connected_repositories. The only mocked boundary is the GitHub repository listing.test_create_and_run_picks_the_installation_that_reaches_the_repositorycovers the multi-installation pick, the unsynced fallback, and one installation that reaches every repository of a multi-repository task. It fails on base.test_create_and_run_non_public_repo_without_a_reaching_installation_raises(renamed and parameterized) covers no installation, no reaching installation, and installations that each reach only some repositories.test_create_and_run_confirms_a_cache_miss_with_githubcatches a stale or empty cache failing creation. It covers GitHub listing the repository and GitHub failing.test_create_and_run_skips_an_installation_github_reports_gonecatches attaching an installation the re-sync found uninstalled.test_create_and_run_bot_task_keeps_off_personal_github_when_the_team_has_an_installationcatches a bot task moving onto a member's credentials.test_create_task_attaches_the_installation_that_reaches_the_repository(API) and itsCreateTaskTooltwin catch the first-installation pick on those paths. The API test also checks that a public sandbox repository makes no GitHub call.test_reports_repositories_from_every_installationcatches a list that reads only the first installation. The mockedtest_archived_repositories_are_excludedis gone, because the DB-backed test next to it covers the same archived and unflagged repositories.test_api.py(full),test_models.py,test_max_tools.py,test_warm_facade.py,test_facade.py, the experiment cleanup PR tests, wizard run creation, and the signals auto-start, report, and installation-resolution tests.Shadow diff (live run on the local dev database). A script called
create_and_run_task(start_workflow=False)andlist_team_connected_repositoriesin a Django shell, on basecdf8d66and on HEAD. Each case ran in a rolled-back transaction with inventedacme/*repositories. The caches were seconds old, so the throttle skipped the GitHub re-sync.acme/widgetsacme/widgets/ install 0acme/widgetsacme/widgetsacme/other/ install 0acme/other/ creation failsacme/widgetsacme/other/ install 0acme/widgetsacme/other/ install 0posthog/hedgeboxacme/other/ install 0acme/other/ install 0Every difference follows from the goal.
Proofs owed and gaps:
Release status
Docs update
None. No doc under
docs/covers how a task picks its GitHub installation.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: Claude Code, Claude Opus 5.5
qa-swarm, because credential selection is a merge-risk danger areaInvolvement: no grilling rounds, no prototypes,
qa-swarmrounds 1 and 2 plus a final read (GPT-6.1 Sol review lane, Opus 5.5 fresh eyes; Team lane skipped), shadow-diff proof captured.tdd,writing-tests,merge-risk,qa-swarm,pr-description,writing-pr-descriptions,writing-code-comments.experiment_service.py). It can call a repository ambiguous when a later installation reaches it.🤖 Generated with Claude Code