feat(code): multi-repo support for cloud tasks#68673
Conversation
Migration SQL ChangesHey 👋, we've detected some migrations on this PR. Here's the SQL output for each migration, make sure they make sense:
|
🔍 Migration Risk AnalysisWe've analyzed your migrations for potential risks. Summary: 0 Safe | 1 Needs Review | 0 Blocked
|
|
Reviews (1): Last reviewed commit: "chore: update OpenAPI generated types" | Re-trigger Greptile |
| additional_clones: list[PreparedClone] = [] | ||
| if should_inject_github_token: | ||
| for additional_repository in ctx.additional_repositories: | ||
| try: | ||
| additional_token = ( | ||
| get_sandbox_github_token( | ||
| ctx.github_integration_id, | ||
| run_id=ctx.run_id, | ||
| state=ctx.state, | ||
| task=task, | ||
| repository=additional_repository, | ||
| ) | ||
| or "" | ||
| ) | ||
| except Exception as e: | ||
| raise GitHubAuthenticationError( | ||
| f"Failed to get GitHub token for additional repository {additional_repository}", | ||
| {"repository": additional_repository, "task_id": ctx.task_id, "error": str(e)}, | ||
| cause=e, | ||
| ) | ||
| additional_clones.append( | ||
| PreparedClone( | ||
| repository=additional_repository, | ||
| github_token=additional_token, | ||
| shallow_clone=shallow_clone, | ||
| ) | ||
| ) |
There was a problem hiding this comment.
Silent skip of additional repos when no GitHub token
additional_clones is only populated when should_inject_github_token is True. If it is False (e.g., has_github_credentials is unset), every entry in ctx.additional_repositories is silently dropped — no warning is logged and the workflow proceeds without cloning those repos. A task that lists public additional repos alongside an unauthenticated primary clone would end up with the extra repos missing and no indication to the user. Consider at minimum emitting a debug log when ctx.additional_repositories is non-empty but should_inject_github_token is False, similar to the "Creating environment without repository" log already present.
🤖 CI report
|
| File | Size | Δ vs base |
|---|---|---|
render-query/src/render-query/render-query.js |
25.44 MiB | 🔺 +1.1 KiB (+0.0%) |
Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report
✅ Eager graph — within budget
How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.
| Root | Eager (shipped) | Δ vs base | Budget |
|---|---|---|---|
entry (logged-out pages, app bootstrap)src/index.tsx |
1.20 MiB · 22 files | no change | ███░░░░░░░ 28.0% of 4.29 MiB |
authenticated shell (every logged-in page)src/scenes/AuthenticatedShell.tsx |
11.58 MiB · 2,951 files | no change | █████████░ 89.9% of 12.87 MiB |
🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
Largest files eagerly shipped from src/index.tsx
| Size | File |
|---|---|
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 24.6 KiB | ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js |
| 6.3 KiB | ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js |
| 4.5 KiB | ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js |
| 3.9 KiB | ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js |
| 1.4 KiB | ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js |
| 1.3 KiB | src/RootErrorBoundary.tsx |
| 912 B | ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js |
| 789 B | src/scenes/ChunkLoadErrorBoundary.tsx |
| 668 B | src/index.tsx |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 458.3 KiB | ../node_modules/.pnpm/@posthog+brand@0.3.0_react@18.3.1/node_modules/@posthog/brand/dist/generated/hoggies/svg/driving-hogzilla.mjs |
| 302.9 KiB | ../node_modules/.pnpm/@posthog+brand@0.3.0_react@18.3.1/node_modules/@posthog/brand/dist/generated/hoggies/svg/coffee-run.mjs |
| 275.2 KiB | ../node_modules/.pnpm/posthog-js@1.396.9/node_modules/posthog-js/dist/rrweb.js |
| 266.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.37.4_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 253.2 KiB | ../node_modules/.pnpm/@posthog+brand@0.3.0_react@18.3.1/node_modules/@posthog/brand/dist/generated/hoggies/svg/chart-hog.mjs |
| 239.2 KiB | ../node_modules/.pnpm/@posthog+brand@0.3.0_react@18.3.1/node_modules/@posthog/brand/dist/generated/hoggies/svg/x-ray.mjs |
| 224.6 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 213.1 KiB | ../node_modules/.pnpm/posthog-js@1.396.9/node_modules/posthog-js/dist/module.js |
| 211.6 KiB | ../node_modules/.pnpm/@posthog+brand@0.3.0_react@18.3.1/node_modules/@posthog/brand/dist/generated/hoggies/svg/money.mjs |
| 185.9 KiB | ../node_modules/.pnpm/@posthog+brand@0.3.0_react@18.3.1/node_modules/@posthog/brand/dist/generated/hoggies/svg/hogpatch.mjs |
Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479
✅ Dist folder size — 🟢 -289.33 MiB (-16.5%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 1466.36 MiB · 🟢 -289.33 MiB (-16.5%)
e748e0c to
676815d
Compare
676815d to
3c4059a
Compare
- fix stale workflow comment: additional_clones is built on resume too - log when additional repositories are skipped for lack of GitHub credentials - drop the primary repo from additional_repositories at the model so a PATCH without `repository` can't reintroduce it - don't pass --additionalDirectories for repos that were never cloned - pass additional_repositories in the ee TaskDetailDTO test construction (mypy) Generated-By: PostHog Code Task-Id: 14a9edb6-2aee-41c8-b1e1-618f8189ecdc
👀 Auto-assigned reviewersThese soft owners were skipped because they only have minor changes here. Nothing blocks merge, so self-assign if you'd like a look:
Soft owners come from |
|
Reviews (2): Last reviewed commit: "fix(code): address multi-repo review com..." | Re-trigger Greptile |
| def _normalize_repository_identifier(value: str) -> str: | ||
| """Validate an ``organization/repo`` identifier and return it lowercased.""" | ||
| parts = value.split("/") | ||
| if len(parts) != 2 or not parts[0] or not parts[1]: |
There was a problem hiding this comment.
Medium: Path traversal in additional repository paths
../.. and org/.. pass this validation and are later converted into paths like /tmp/workspace/repos/../.. for --additionalDirectories. An authenticated user can create or update a task with that extra repository and make the agent server treat /tmp or the repos parent as an allowed workspace directory, which exposes files outside the cloned repos such as the sandbox env file. Reject ./.. and constrain both owner and repo to GitHub-safe path components here and in the model save() validation.
PR overviewThis pull request adds multi-repository support for cloud tasks, allowing tasks to reference additional repositories that are passed to the agent as extra workspace directories. The touched backend serializer logic handles creation and update validation for those additional repository entries. There is one open security issue in the additional-repository validation path. Because repository path components are not sufficiently constrained, an authenticated user could submit traversal segments that cause the agent to treat directories outside the cloned repositories as allowed workspace locations, potentially exposing sensitive local files such as sandbox environment data. No issues have been addressed yet, so the current security posture still depends on tightening this validation before merge. Open issues (1)
Fixed/addressed: 0 · PR risk: 7/10 |

Problem
it's not intuitive to have a cloud task work across more than one repo. current common workaround is just prompting the agent to clone another repo
code app PR: PostHog/code#3184
^ merge after that one is deployed
Changes
this PR only solves the "start a cloud task with >1 repos" problem. multi-PR support & UX in future PRs!
additional_repositoriesfield on theTaskmodelHow did you test this code?
manually, local dev
👉 Stay up-to-date with PostHog coding conventions for a smoother review.
Automatic notifications
Docs update
🤖 Agent context
Autonomy: Human-driven (agent-assisted) - or - Fully autonomous