feat(tasks): rebind sandbox GitHub identity on actor transitions#70668
Closed
VojtechBartos wants to merge 5 commits into
Closed
feat(tasks): rebind sandbox GitHub identity on actor transitions#70668VojtechBartos wants to merge 5 commits into
VojtechBartos wants to merge 5 commits into
Conversation
The follow-up MCP refresh gate skipped whenever any token had been issued for the run within the freshness window, so when a different user spoke next (multiplayer Slack threads), the live session kept the previous actor's OAuth token for up to the full window. Key the freshness mark per (sandbox, user) and remember which identity the session was last bound to: a transition bypasses the window and rebinds immediately, while repeat messages from the same actor still skip the redundant refresh. Scoping the marks to the sandbox id also means a replacement sandbox (retry, snapshot restore) starts unmarked and gets a fresh token instead of inheriting a stale run-keyed mark.
The field records whose credentials the sandbox booted with; the MCP prefix undersells it as more surfaces (GitHub) adopt the same mark.
VojtechBartos
force-pushed
the
vojtab/sandbox-github-identity-transitions
branch
from
July 14, 2026 12:00
93c1690 to
0b36040
Compare
The marks belong to the tasks product, matching the existing tasks: key convention (e.g. the GitHub token rotation lock), not posthog_ai.
Unify the per-message identity reconciliation for MCP and GitHub in one gate (ensure_sandbox_identity), called right before a follow-up turn is delivered. The MCP surface keeps its transition-or-freshness semantics; the GitHub surface rebinds on actor transitions only, re-injecting the actor's GitHub token and git author into the live sandbox and notifying the agent-server with the effective PR authorship. Token TTL between messages stays owned by the credential-refresh loop. Also fix the credential-refresh loop reading the workflow-start snapshot of the run state: it now re-reads the state live, so cadence refreshes mint for the run's current actor instead of the boot-time one.
Applies review findings across the identity-gate stack: - Pass the caller's already-resolved actor into ensure_sandbox_identity instead of re-resolving it (2-3 redundant DB queries per follow-up). - Fold the git-author write into GitHubSandboxCredential so every credential write (identity gate and refresh loop alike) applies token and author together, and share one agent-server notify helper between the loop and the gate. - Add TaskProcessingContext.with_state as the single owner of the boot-snapshot-to-live-state rebase, and fetch the TaskRun once per refresh tick instead of twice. - Collapse the duplicated send/retry success blocks into a loop, share the mark-pair helper (utils.mark_mcp_session) with the boot path, and name the creator-default assumption once (_last_bound_identity). - Guard the GitHub gate on sandbox_id before logging a transition. - Tests: shared mock helpers module, class-level patch stacks, cache.clear() fixture instead of a hand-enumerated key list.
VojtechBartos
force-pushed
the
vojtab/sandbox-github-identity-transitions
branch
from
July 14, 2026 12:12
0b36040 to
bb2c977
Compare
VojtechBartos
force-pushed
the
vojtab/mcp-session-identity-transitions
branch
21 times, most recently
from
July 20, 2026 09:21
feec41d to
54a3240
Compare
VojtechBartos
force-pushed
the
vojtab/mcp-session-identity-transitions
branch
from
July 20, 2026 10:12
54a3240 to
02268db
Compare
Member
Author
|
Superseded by #71941, which does rebind or log out on failure (a superset of the rebind-only behavior here) and is built on the current, rebased PR3. Closing this stale branch. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Completes per-message identity for multiplayer Slack threads. #70454 makes the sandbox's MCP session follow the current speaker; this PR does the same for GitHub, and unifies both behind one gate so identity switching always happens in the same place.
Two GitHub-specific gaps today:
refresh_sandbox_credentialsreceives theTaskProcessingContextcaptured at workflow start, whosestateis a boot-time snapshot, so even its cadence refreshes resolve the boot-time actor forever. The actor-aware resolution added in feat(slack): add approval recovery flow #66212 never sees mid-run actor changes.Changes
One reconciliation gate,
ensure_sandbox_identity(newsandbox_identity.pymodule), called from the follow-up delivery activity right before the turn runs. It compares the run's current actor against the identity last pushed to this sandbox, per surface:flowchart TD M[Follow-up message arrives] --> A{{Resolve actor from run state}} A --> MCP{MCP: transition or freshness window expired?} A --> GH{GitHub: transition?} MCP -- yes --> RM[Mint OAuth token, push refresh_session] GH -- yes --> RG[Re-inject GitHub token + git author, notify authorship] RM --> K[(Sandbox-scoped identity + freshness marks)] RG --> K classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff; classDef phYellow fill:#f9bd2b,stroke:#f9bd2b,color:#000; classDef phGray fill:#e5e7eb,stroke:#c7ccd1,color:#000; class A,RM,RG phBlue; class M phYellow; class K phGray;send_followup_to_sandbox.pyunchanged in behavior (transition bypasses the freshness window; same-actor repeats within the window skip).GitHubSandboxCredential.refreshwith the live run state (actor-aware token via feat(slack): add approval recovery flow #66212's resolution), rewrites the git author env (get_git_identity_env_vars), and notifies the agent-server withrefreshed_credentials=["github"]and the effective PR authorship. A failed rebind leaves the mark untouched, so the next message retries.refresh_sandbox_credentialsre-reads the run state live before building credentials, so cadence refreshes also follow the current actor.context(the run'sTaskProcessingContext, passed by both workflows); activities scheduled by pre-rollout histories deserialize without it and simply skip the GitHub surface.Note
Stacked on #70454 (the diff base). The gate stays best-effort: a failed rebind never blocks follow-up delivery.
How did you test this code?
327 passed, 4 skipped across
process_task+execute_sandboxsuites locally. New coverage intest_sandbox_identity.py:TestGithubIdentityTransitionGate(new): a speaker change re-injects credentials with the live run state, rewrites the git author, notifies with authorship, and records the identity; same actor doesn't touch the sandbox; missing context / no GitHub credentials / missing sandbox id skip safely; a failed rebind leaves the mark unset so the next message retries; an unrefreshable credential (caller-token run) marks without notifying.test_send_followup_to_sandbox.pyand retargeted to the shared gate, asserting identical behavior throughensure_sandbox_identity.Not manually tested end to end; the log trail (
refresh_github_identity_transition,refresh_github_delivered,refresh_github_identity_failed) mirrors the MCP one for rollout verification.Automatic notifications
Docs update
Internal behavior only, no docs affected.
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Claude Code (Claude Fable 5). Replaces closed #70024, rebuilt on master's run-state actor design. The gate extraction (rather than a second standalone refresh function) was an explicit ask: MCP and GitHub identity switching happen in the same place with the same transition semantics.