ZhuLinsen/daily_stock_analysis · critical · CodexAppServerError

permission_profile_mismatch

permission_profile_mismatch

Error message

App Server did not activate the DSA permission profile

What it means

start_thread() requests the DSA permission profile (constant PERMISSION_PROFILE = 'dsa_gate_a'). After validating the thread id, it checks result['activePermissionProfile']['id'] equals that profile. A mismatch (different profile id, or activePermissionProfile absent so .get('id') is None) raises code 'permission_profile_mismatch' — the server did not honor the requested sandbox permissions and the thread must not be used.

Source

Thrown at src/agent/codex_app_server_transport.py:437

            {
                "approvalPolicy": "never",
                "baseInstructions": base_instructions,
                "cwd": str(self.safe_cwd),
                "developerInstructions": developer_instructions,
                "dynamicTools": dynamic_tool_specs(self.tool_surface, tool_names),
                "environments": [],
                "ephemeral": True,
                "permissions": PERMISSION_PROFILE,
                "runtimeWorkspaceRoots": [str(self.safe_cwd)],
            },
        )
        thread = result.get("thread") or {}
        thread_id = thread.get("id")
        if not isinstance(thread_id, str) or not thread_id:
            raise CodexAppServerError("protocol_error", "thread/start did not return a thread id")
        active_profile = result.get("activePermissionProfile") or {}
        if active_profile.get("id") != PERMISSION_PROFILE:
            raise CodexAppServerError(
                "permission_profile_mismatch",
                "App Server did not activate the DSA permission profile",
            )
        with self._state_lock:
            self._thread_tools[thread_id] = set(tool_names)
            self._thread_metadata[thread_id] = {
                "active_permission_profile": active_profile,
                "approval_policy": result.get("approvalPolicy"),
                "cwd_matches": result.get("cwd") == str(self.safe_cwd),
                "runtime_roots_match": result.get("runtimeWorkspaceRoots") == [str(self.safe_cwd)],
                "sandbox": result.get("sandbox"),
            }
        return thread_id

    def thread_metadata(self, thread_id: str) -> dict:
        with self._state_lock:
            return dict(self._thread_metadata.get(thread_id, {}))

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Confirm the installed Codex App Server knows the permission profile 'dsa_gate_a' (check its docs/config for supported profile ids)
  2. Pin the codex binary version tested with this transport; upgrade the transport's PERMISSION_PROFILE constant only together with a verified server
  3. Log the raw thread/start result to see what activePermissionProfile (if any) the server returned
  4. Treat this as fail-closed: never proceed with a thread whose effective permissions are unknown

Example fix

# before: transport requests profile 'dsa_gate_a'
result = client.request("thread/start", {..., "permissions": "dsa_gate_a"})
# server returns {"activePermissionProfile": {"id": "default"}} -> mismatch

# after: align server and transport on one profile id
# server side: activate the requested profile verbatim
# transport side (after verified protocol upgrade):
# PERMISSION_PROFILE = "dsa_gate_v2"  # only with a matching server
Defensive patterns

Strategy: try-catch

Type guard

def profile_activated(result: dict, profile: str = "dsa_gate_a") -> bool:
    active = result.get("activePermissionProfile") or {}
    return isinstance(active, dict) and active.get("id") == profile

Try / catch

try:
    thread_id = client.start_thread(...)
except CodexAppServerError as exc:
    if exc.code == "permission_profile_mismatch":
        fail_closed("server refused DSA permission profile; do not run with unknown permissions")
    raise

Prevention

When it happens

Trigger: The installed Codex App Server does not support the named permission profile and silently activates its default; a server bug ignores the 'permissions' field of thread/start; the profile id was renamed in a newer protocol version; a response envelope change moves the field so activePermissionProfile comes back empty.

Common situations: Upgrading the codex binary to a version where 'dsa_gate_a' is unknown or renamed; running the transport against a mock that echoes params but not the active profile; enterprise-managed Codex configs forcing a different default profile.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/d01af22da7774fad. Report an issue: GitHub.