feat(seer): Fall back to contextvar ViewerContext for Seer requests#112221
feat(seer): Fall back to contextvar ViewerContext for Seer requests#112221
Conversation
When no explicit viewer_context is passed to make_signed_seer_api_request, read from the contextvar set by ViewerContextMiddleware. If both exist, explicit fields override but a mismatch logs a warning and strips the token from the payload as a safety measure. Also serializes actor_type and token (kind + scopes) when available, enriching the context Seer receives. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
||
| def _resolve_viewer_context( | ||
| explicit: SeerViewerContext | None, | ||
| ) -> dict[str, Any] | None: |
There was a problem hiding this comment.
is it possible to type the dict more narrowly with e.g. a TypedDict?
|
|
||
|
|
||
| @sentry_sdk.tracing.trace | ||
| def make_signed_seer_api_request( |
There was a problem hiding this comment.
does it make sense to add any tests to this function based on the viewer context possibilities?
0b25b89 to
188f576
Compare
188f576 to
cd16fea
Compare
Address review feedback: add tests covering all branches of the viewer context resolution (contextvar only, explicit only, merge, mismatch warning, token stripping). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cd16fea to
f6e109e
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| result["user_id"] = self.user_id | ||
| if self.token is not None: | ||
| result["token"] = {"kind": self.token.kind, "scopes": list(self.token.get_scopes())} | ||
| return result |
There was a problem hiding this comment.
Wire format change breaks existing callers' X-Viewer-Context header
Medium Severity
The X-Viewer-Context header wire format changes for all existing callers even when the contextvar middleware is disabled. Previously, orjson.dumps(viewer_context) serialized SeerViewerContext as {"organization_id": X, "user_id": Y}. Now, orjson.dumps(resolved.serialize()) always includes "actor_type": "unknown" (and potentially "token") in the payload. The PR claims "Existing callers that pass explicit context continue to work unchanged," but the Seer service now receives a different JSON shape for every request that includes viewer context.
Additional Locations (1)
Backend Test FailuresFailures on
|
ViewerContext.serialize() now always includes actor_type. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>


make_signed_seer_api_requestnow auto-reads from theViewerContextcontextvar when no explicitviewer_contextparameter is passed. This means Seer calls made during API requests (where the middleware sets the contextvar) automatically get identity context without callers needing to construct and passSeerViewerContext.Behavior:
actor_typeandtoken(kind + scopes) when availableSeerViewerContextparam overridesorganization_id/user_idif passedseer.viewer_context_mismatchwarning and strips the token from the payloadThis is safe to land independently — it's a no-op until the ViewerContext middleware is enabled (
viewer-context.enabledoption). Existing callers that pass explicit context continue to work unchanged.Part of the ViewerContext RFC.