Skip to content

PermissionRequest.action round-trip turns None into PermissionRequestMemoryAction.STORE #1141

@007bsd

Description

@007bsd

Summary

PermissionRequest.from_dict() reads a missing action field as the literal string "store", which then gets parsed as the enum value PermissionRequestMemoryAction.STORE instead of None. Round-tripping from_dict(to_dict(x)) for any PermissionRequest with action=None silently turns it into a STORE action.

This is the same shape as already-fixed bug #1139 (SessionTaskCompleteData.summary), in a different class.

Affected versions

main at commit dd2dcbc439256acfb9feb2cff07c0b9c820091b8. Generated file; same logic ships in every Python release built from this codegen.

Affected source

  • python/copilot/generated/session_events.py:1845 — the misdefault on action:
    action = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryAction, x)], obj.get("action", "store"))

Compare the immediately-adjacent sibling field on line 1846, which is correct (no default):

args = obj.get("args")

The dataclass declaration (line 1812) has the right in-memory default:

action: PermissionRequestMemoryAction | None = None

to_dict() correctly omits action when it is None. The asymmetry is in from_dict only.

Reproduction

from copilot.generated.session_events import PermissionRequest, PermissionRequestKind

original = PermissionRequest(kind=PermissionRequestKind.MEMORY)
print("original.action:  ", original.action)
print("to_dict() has 'action' key?", "action" in original.to_dict())
roundtrip = PermissionRequest.from_dict(original.to_dict())
print("roundtrip.action: ", roundtrip.action)
print("equal?            ", roundtrip == original)

Expected vs actual output

Expected:

original.action:   None
to_dict() has 'action' key? False
roundtrip.action:  None
equal?             True

Actual:

original.action:   None
to_dict() has 'action' key? False
roundtrip.action:  PermissionRequestMemoryAction.STORE
equal?             False

Root cause

The obj.get("action", "store") default coerces a missing key to the string "store". from_union([from_none, parse_enum], "store") then succeeds via parse_enum, producing PermissionRequestMemoryAction.STORE — the enum value whose wire representation is "store".

The sibling field on line 1846 (args) does it correctly with obj.get("args") and no default, so missing keys flow through correctly and produce None.

This is the same misdefault pattern as already-fixed bug #1139 (SessionTaskCompleteData.summary), where obj.get("summary", "") was changed to obj.get("summary") in line 2816.

Impact

  • PermissionRequest is part of the permission-handling flow in python/copilot/session.py. Any permission request without an action field becomes a STORE action after parsing.
  • Code that pattern-matches on action == None to mean "no memory action requested" sees STORE instead and may take an unintended branch.
  • Round-tripping (caching, replay, audit logs) silently mutates the field.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions