fix(copilot): guard request_flags against a non-dict last message (#5274)

request_flags derives (agent, vision) and does last.get("role") after only
a truthy check. A client can send a bare-string message element
("messages": ["hi"]), and the vision loop right below already guards each
element with isinstance — so the .get() on a non-dict last element is an
oversight that raises AttributeError on every Copilot-proxied request with
such a body.

Use isinstance(last, dict) to match the loop's own guard.

Fixes #5273

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Wes Huber
2026-07-08 14:57:23 -07:00
committed by GitHub
parent 3064819e3d
commit a35384e68f
2 changed files with 16 additions and 1 deletions
+4 -1
View File
@@ -230,7 +230,10 @@ def request_flags(messages) -> tuple:
"""
msgs = messages or []
last = msgs[-1] if msgs else None
agent = bool(last) and last.get("role") != "user"
# A message element can be a non-dict (clients send `"messages": ["hi"]`);
# the vision loop below already guards each element with isinstance, so do
# the same here rather than call .get() on a bare string.
agent = isinstance(last, dict) and last.get("role") != "user"
vision = False
for m in msgs:
content = m.get("content") if isinstance(m, dict) else None
+12
View File
@@ -89,6 +89,18 @@ def test_request_flags_vision():
assert vision is True
def test_request_flags_non_dict_last_message_does_not_crash():
# A client can send a bare-string (non-dict) last element; before the
# isinstance guard this raised AttributeError on last.get("role").
assert copilot.request_flags(["hi"]) == (False, False)
assert copilot.request_flags([{"role": "user"}, "trailing"]) == (False, False)
def test_request_flags_empty_and_none():
assert copilot.request_flags([]) == (False, False)
assert copilot.request_flags(None) == (False, False)
def test_apply_request_headers_mutates():
h = {"X-GitHub-Api-Version": "v"}
copilot.apply_request_headers(h, [{"role": "tool", "content": "x"}])