Summary
PermissionPromptRequest.from_dict() reads a missing action field as the literal string "store", which then gets parsed as the enum value PermissionPromptRequestMemoryAction.STORE instead of None. Round-tripping from_dict(to_dict(x)) for any PermissionPromptRequest 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:1695 — the misdefault on action:
action = from_union([from_none, lambda x: parse_enum(PermissionPromptRequestMemoryAction, x)], obj.get("action", "store"))
Compare the immediately-adjacent sibling field on line 1694, which is correct (no default):
access_kind = from_union([from_none, lambda x: parse_enum(PermissionPromptRequestPathAccessKind, x)], obj.get("accessKind"))
The dataclass declaration (line 1664) has the right in-memory default:
action: PermissionPromptRequestMemoryAction | None = None
And to_dict() correctly omits action when it is None (line 1755):
if self.action is not None:
result["action"] = ...
The asymmetry is in from_dict only.
Reproduction
from copilot.generated.session_events import PermissionPromptRequest, PermissionPromptRequestKind
original = PermissionPromptRequest(kind=PermissionPromptRequestKind.MEMORY)
print("original.action: ", original.action)
print("to_dict() has 'action' key?", "action" in original.to_dict())
roundtrip = PermissionPromptRequest.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: PermissionPromptRequestMemoryAction.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 PermissionPromptRequestMemoryAction.STORE — the enum value whose wire representation is "store".
The sibling field on line 1694 (access_kind) does it correctly with obj.get("accessKind") and no default, so missing keys flow through from_none 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
PermissionPromptRequest is dispatched by 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.
Summary
PermissionPromptRequest.from_dict()reads a missingactionfield as the literal string"store", which then gets parsed as the enum valuePermissionPromptRequestMemoryAction.STOREinstead ofNone. Round-trippingfrom_dict(to_dict(x))for anyPermissionPromptRequestwithaction=Nonesilently turns it into aSTOREaction.This is the same shape as already-fixed bug #1139 (
SessionTaskCompleteData.summary), in a different class.Affected versions
mainat commitdd2dcbc439256acfb9feb2cff07c0b9c820091b8. Generated file; same logic ships in every Python release built from this codegen.Affected source
python/copilot/generated/session_events.py:1695— the misdefault onaction:Compare the immediately-adjacent sibling field on line 1694, which is correct (no default):
The dataclass declaration (line 1664) has the right in-memory default:
And
to_dict()correctly omitsactionwhen it isNone(line 1755):The asymmetry is in
from_dictonly.Reproduction
Expected vs actual output
Expected:
Actual:
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 viaparse_enum, producingPermissionPromptRequestMemoryAction.STORE— the enum value whose wire representation is"store".The sibling field on line 1694 (
access_kind) does it correctly withobj.get("accessKind")and no default, so missing keys flow throughfrom_noneand produceNone.This is the same misdefault pattern as already-fixed bug #1139 (
SessionTaskCompleteData.summary), whereobj.get("summary", "")was changed toobj.get("summary")in line 2816.Impact
PermissionPromptRequestis dispatched by the permission-handling flow inpython/copilot/session.py. Any permission request without anactionfield becomes aSTOREaction after parsing.action == Noneto mean "no memory action requested" seesSTOREinstead and may take an unintended branch.