* Add task owner git auth proposal and sandbox architecture docs - Add proposal for primary user per task with OAuth token injection for sandbox git operations (.context/proposal-task-owner-git-auth.md) - Document sandbox architecture constraints in CLAUDE.md: single sandbox per task assumption, OAuth token security implications, git auto-auth requirement, and git error surfacing rules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add proposals for reverting to queues and rivetkit sandbox resilience - proposal-revert-actions-to-queues.md: Detailed plan for reverting the actions-only pattern back to queues/workflows now that the RivetKit queue.iter() bug is fixed. Lists what to keep (lazy tasks, resolveTaskRepoId, sync override threading, E2B fixes, frontend fixes) vs what to revert (communication pattern only). - proposal-rivetkit-sandbox-resilience.md: Rivetkit sandbox actor changes for handling destroyed/paused sandboxes, keep-alive, and the UNIQUE constraint crash fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(foundry): add manual task owner change via UI dropdown Add an owner dropdown to the Overview tab that lets users reassign task ownership to any organization member. The owner's GitHub credentials are used for git operations in the sandbox. Full-stack implementation: - Backend: changeTaskOwnerManually action on task actor, routed through org actor's changeWorkspaceTaskOwner action, with primaryUser schema columns on both task and org index tables - Client: changeOwner method on workspace client (mock + remote) - Frontend: owner dropdown in right sidebar Overview tab showing org members, with avatar and role display - Shared: TaskWorkspaceChangeOwnerInput type and primaryUser fields on workspace snapshot types Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8.4 KiB
Proposal: Task Primary Owner & Git Authentication
Problem
Sandbox git operations (commit, push, PR creation) require authentication. Currently, the sandbox has no user-scoped credentials. The E2B sandbox clones repos using the GitHub App installation token, but push operations need user-scoped auth so commits are attributed correctly and branch protection rules are enforced.
Design
Concept: Primary User per Task
Each task has a primary user (the "owner"). This is the last user who sent a message on the task. Their GitHub OAuth credentials are injected into the sandbox for git operations. When the owner changes, the sandbox git config and credentials swap to the new user.
Data Model
Task actor DB -- new task_owner single-row table:
primaryUserId(text) -- better-auth user IDprimaryGithubLogin(text) -- GitHub username (forgit config user.name)primaryGithubEmail(text) -- GitHub email (forgit config user.email)primaryGithubAvatarUrl(text) -- avatar for UI displayupdatedAt(integer)
Org coordinator -- add to taskSummaries table:
primaryUserLogin(text, nullable)primaryUserAvatarUrl(text, nullable)
Owner Swap Flow
Triggered when sendWorkspaceMessage is called with a different user than
the current primary:
sendWorkspaceMessage(authSessionId, ...)resolves user from auth session- Look up user's GitHub identity from auth account table (
providerId = "github") - Compare
primaryUserIdwith current owner. If different: a. Updatetask_ownerrow in task actor DB b. Get user's OAuthaccessTokenfrom auth account c. Push into sandbox viarunProcess:git config user.name "{login}"git config user.email "{email}"- Write token to
/home/user/.git-token(or equivalent) d. Push updated task summary to org coordinator (includesprimaryUserLogin) e. BroadcasttaskUpdatedto connected clients
- If same user, no-op (token is still valid)
Token Injection
The user's GitHub OAuth token (stored in better-auth account table) has
repo scope (verified -- see better-auth.ts line 480: scope: ["read:org", "repo"]).
This is a standard OAuth App flow (not GitHub App OAuth). OAuth App tokens do not expire unless explicitly revoked. No refresh logic is needed.
Injection method:
On first sandbox repo setup (ensureSandboxRepo), configure:
# Write token file
echo "{token}" > /home/user/.git-token
chmod 600 /home/user/.git-token
# Configure git to use it
git config --global credential.helper 'store --file=/home/user/.git-token'
# Format: https://{login}:{token}@github.com
echo "https://{login}:{token}@github.com" > /home/user/.git-token
On owner swap, overwrite /home/user/.git-token with new user's credentials.
Important: git should never prompt for credentials. The credential
store file ensures all git operations are auto-authenticated. No
GIT_ASKPASS prompts, no interactive auth.
Race condition (expected behavior): If User A sends a message and the agent starts a long git operation, then User B sends a message and triggers an owner swap, the in-flight git process still has User A's credentials (already read from the credential store). The next git operation uses User B's credentials. This is expected behavior -- document in comments.
Token Validity
OAuth App tokens (our flow) do not expire. They persist until the user revokes them or the OAuth App is deauthorized. No periodic refresh needed.
If a token becomes invalid (user revokes), git operations will fail with
a 401. The error surfaces through the standard ensureSandboxRepo /
runProcess error path and is displayed in the UI.
User Removal
When a user is removed from the organization:
- Org actor queries active tasks with that user as primary owner
- For each, clear the
task_ownerrow - Task actor clears the sandbox git credentials (overwrite credential file)
- Push updated task summaries to org coordinator
- Subsequent git operations fail with "No active owner -- assign an owner to enable git operations"
UI Changes
Right sidebar -- new "Overview" tab:
- Add as a new tab alongside "Changes" and "All Files"
- Shows current primary user: avatar, name, login
- Click on the user -> dropdown of all workspace users (from org member list)
- Select a user -> triggers explicit owner swap (same flow as message-triggered)
- Also shows task metadata: branch, repo, created date
Left sidebar -- task items:
- Show primary user's GitHub login in green text next to task name
- Only shown when there is an active owner
Task detail header:
- Show small avatar of primary user next to task title
Org Coordinator
commandApplyTaskSummaryUpdate already receives the full task summary
from the task actor. Add primaryUserLogin and primaryUserAvatarUrl
to the summary payload. The org writes it to taskSummaries. The sidebar
reads it from the org snapshot.
Sandbox Architecture Note
Structurally, the system supports multiple sandboxes per task, but in practice there is exactly one active sandbox per task. Design the owner injection assuming one sandbox. The token is injected into the active sandbox only. If multi-sandbox support is needed in the future, extend the injection to target specific sandbox IDs.
Security Considerations
OAuth Token Scope
The user's GitHub OAuth token has repo scope, which grants full control
of all private repositories the user has access to. When injected into
the sandbox:
- The agent can read/write ANY repo the user has access to, not just the task's target repo
- The token persists in the sandbox filesystem until overwritten
- Any process running in the sandbox can read the credential file
Mitigations:
- Credential file has
chmod 600(owner-read-only) - Sandbox is isolated per-task (E2B VM boundary)
- Token is overwritten on owner swap (old user's token removed)
- Token is cleared on user removal from org
- Sandbox has a finite lifetime (E2B timeout + autoPause)
Accepted risk: This is the standard trade-off for OAuth-based git
integrations (same as GitHub Codespaces, Gitpod, etc.). The user consents
to repo scope at sign-in time. Document this in user-facing terms in
the product's security/privacy page.
Future: Fine-grained tokens
GitHub supports fine-grained personal access tokens scoped to specific repos. A future improvement could mint per-repo tokens instead of using the user's full OAuth token. This requires the user to create and manage fine-grained tokens, which adds friction. Evaluate based on user feedback.
Implementation Order
- Add
task_ownertable to task actor schema + migration - Add
primaryUserLogin/primaryUserAvatarUrltotaskSummariesschema + migration - Implement owner swap in
sendWorkspaceMessageflow - Implement credential injection in
ensureSandboxRepo - Implement credential swap via
runProcesson owner change - Implement user removal cleanup in org actor
- Add "Overview" tab to right sidebar
- Add owner display to left sidebar task items
- Add owner picker dropdown in Overview tab
- Update org coordinator to propagate owner in task summaries
Files to Modify
Backend
foundry/packages/backend/src/actors/task/db/schema.ts-- addtask_ownertablefoundry/packages/backend/src/actors/task/db/migrations.ts-- add migrationfoundry/packages/backend/src/actors/organization/db/schema.ts-- add owner columns totaskSummariesfoundry/packages/backend/src/actors/organization/db/migrations.ts-- add migrationfoundry/packages/backend/src/actors/task/workspace.ts-- owner swap logic insendWorkspaceMessage, credential injection inensureSandboxRepofoundry/packages/backend/src/actors/task/workflow/index.ts-- wire owner swap actionfoundry/packages/backend/src/actors/organization/actions/task-mutations.ts-- propagate owner in summariesfoundry/packages/backend/src/actors/organization/actions/tasks.ts--sendWorkspaceMessageowner checkfoundry/packages/backend/src/services/better-auth.ts-- exposegetAccessTokenForSessionfor owner lookup
Shared
foundry/packages/shared/src/types.ts-- addprimaryUserLogintoTaskSummary
Frontend
foundry/packages/frontend/src/components/mock-layout/right-sidebar.tsx-- add Overview tabfoundry/packages/frontend/src/components/organization-dashboard.tsx-- show owner in sidebar task itemsfoundry/packages/frontend/src/components/mock-layout.tsx-- wire Overview tab state