Repository navigation
feat(teams): resolve the team a project's runs act as - #2927
Merged
Merged
Conversation
🎩 PreviewA preview build has been created at: |
2 of 4 tasks
camielvs
force-pushed
the
cvs/team-context
branch
from
October 22, 2026 20:12
93462fd to
1a4a152
Compare
1 of 3 tasks
camielvs
force-pushed
the
cvs/team-context
branch
2 times, most recently
from
October 26, 2026 21:56
18c82f0 to
a62e044
Compare
1 of 3 tasks
camielvs
force-pushed
the
cvs/team-context
branch
from
October 27, 2026 17:33
a62e044 to
2817cb1
Compare
morgan-wowk
previously requested changes
Oct 28, 2026
morgan-wowk
left a comment
There was a problem hiding this comment.
Partial review. Left one blocking comment for now.
morgan-wowk
self-requested a review
October 29, 2026 18:57
camielvs
requested review from
morgan-wowk
and removed request for
morgan-wowk
October 29, 2026 19:49
camielvs
force-pushed
the
cvs/team-context
branch
from
October 29, 2026 20:21
2817cb1 to
a4e93cd
Compare
camielvs
commented
Oct 29, 2026
camielvs
force-pushed
the
cvs/team-context
branch
from
October 29, 2026 23:33
a4e93cd to
9e96dd7
Compare
camielvs
commented
Oct 30, 2026
camielvs
commented
Oct 30, 2026
camielvs
commented
Oct 30, 2026
camielvs
force-pushed
the
cvs/team-context
branch
from
October 30, 2026 18:51
9e96dd7 to
8b24daf
Compare
1 of 3 tasks
camielvs
force-pushed
the
cvs/team-context
branch
from
October 30, 2026 20:35
8b24daf to
571f8e8
Compare
camielvs
marked this pull request as ready for review
October 30, 2026 21:24
camielvs
force-pushed
the
cvs/team-context
branch
from
October 30, 2026 21:46
571f8e8 to
f0cdb96
Compare
maxy-shpfy
approved these changes
Oct 30, 2026
The foundation for workspace secrets under team impersonation. Inside a project whose workspace maps to a team, secrets are managed and runs are submitted as that team by adding the backend's impersonation header to those specific requests. Nothing here sends the header yet or changes any screen; later changes in this stack draw on it. - `getTeamImpersonationHeaderName` reads the header name the host page injects as `window.__TANGLE_TEAM_IMPERSONATION__`. With none configured, as in an OSS deployment, every team lookup resolves to "none". It refuses a name that isn't a valid header, or that the app's own requests already send, such as `Authorization`, which the team header would overwrite. - `teamRequestHeaders` is the one place the header is built. - `resolveTeamId` is the one place that knows a workspace names its team through `data.teamId`, the interim contract until the backend has a field. - `readTeamMemberships` is the one place that knows the `teams` map on `GET /api/users/me`, which is fetched without the header (with it, the response describes the team and carries no `teams`). `fetchTeamMemberships` is its own lookup rather than the shared user query, which turns a failed request into an anonymous user for 30 minutes, so a failed lookup is an error rather than "not a member". - `fetchTeamIdentity` asks `/api/users/me` with the header. A team is only active when the backend answers as that team: the development backend ignores the header but still lists teams, and a `nobody` role passes impersonation with no access. `canWrite` comes from the permissions the backend returns rather than from role names. - `resolveTeam` decides the outcome from the lookups, and `useTeamForProjects` gathers them: none, resolving, active, not-member, unavailable, conflict (projects on different teams) or error. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
camielvs
force-pushed
the
cvs/team-context
branch
from
October 31, 2026 00:12
f0cdb96 to
2fe4639
Compare
1 of 3 tasks
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.
Description
The first of three foundation PRs for workspace secrets under team impersonation. Inside a project whose workspace maps to a team, secrets are managed and runs are submitted as that team: the backend's impersonation header is added to those specific requests only, never globally. This PR decides which team, if any, a set of projects acts as. Nothing here sends the header or changes a screen yet.
getTeamImpersonationHeaderNamereads the header name the host page injects aswindow.__TANGLE_TEAM_IMPERSONATION__. With none configured, as in a default deployment, every team lookup resolves tononeand the app behaves exactly as today. It also refuses a name that isn't a valid header, or one the app's own requests already send, such asAuthorization, which the team header would overwrite.teamRequestHeaders(teamId)is the one place the header is built. It returns{}without a configured header name or a team id.resolveTeamIdis the one place that knows a workspace names its team throughdata.teamId, the interim contract until the backend has a field for it.readTeamMembershipsis the one place that knows theteamsmap onGET /api/users/me. That map is fetched without the header; with it, the response describes the team and carries noteams.fetchTeamMembershipsis its own lookup rather than the shared user query, which turns a failed request into an anonymous user for 30 minutes, so a failed lookup reports an error instead of "not a member".fetchTeamIdentityasks/api/users/mewith the header. A team only counts as active when the backend answers as that team. That's because anobodyrole passes impersonation with no access, and a backend that doesn't read the configured header still lists teams but answers as the user.canWritecomes from the permissions the backend returns rather than from role names.resolveTeamturns the lookups into one outcome, anduseTeamForProjectsgathers them. The outcome is one ofnone,resolving,active,not-member,unavailable,conflict(projects on different teams) orerror.Related Issue and Pull requests
Type of Change
Line Breakdown
Checklist
Screenshots (if applicable)
None. There's no UI change.
Test Instructions
pnpm validate(format, lint, typecheck, knip) passes. No screen calls these hooks yet, so there's nothing to click through.Additional Comments
src/services/teamsis added toreact-compiler.config.js.useTeamForProjectsreads projects and workspaces through the existingProjectsQueryKeys/WorkspacesQueryKeyscaches and treats a deleted project as absent rather than as an error.🤖 Generated with Claude Code