#!/usr/bin/env -S uv run --script
"""
Minimal repro: ty 0.0.23 panics when workspace/configuration response
has fewer items than the request's items array.
Usage:
python3 repro.py # Demonstrates the crash (result: [])
python3 repro.py --fix # Shows correct behavior (result: [null])
"""
import json
import subprocess
import sys
import time
def build_msg(msg_dict: dict) -> bytes:
body = json.dumps(msg_dict).encode()
return f"Content-Length: {len(body)}\r\n\r\n".encode() + body
def read_lsp_message(stream):
headers = {}
while True:
line = stream.readline()
if not line:
return None
line = line.decode("utf-8", errors="replace").strip()
if not line:
break
if ":" in line:
key, _, val = line.partition(":")
headers[key.strip().lower()] = val.strip()
length = int(headers.get("content-length", 0))
if length == 0:
return None
body = stream.read(length)
return json.loads(body)
def main():
use_fix = "--fix" in sys.argv
import tempfile, os
workspace = tempfile.mkdtemp(prefix="ty_repro_")
with open(os.path.join(workspace, "example.py"), "w") as f:
f.write("x: int = 1\n")
proc = subprocess.Popen(
["ty", "server"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
workspace_uri = f"file://{workspace}"
init_msg = {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"processId": os.getpid(),
"rootUri": workspace_uri,
"capabilities": {
"workspace": {"workspaceFolders": True, "configuration": True},
},
"workspaceFolders": [{"uri": workspace_uri, "name": "repro"}],
},
}
proc.stdin.write(build_msg(init_msg))
proc.stdin.flush()
init_resp = read_lsp_message(proc.stdout)
server_info = init_resp["result"].get("serverInfo", {})
print(f"1. initialize OK (server: {server_info.get('name')} {server_info.get('version')})")
proc.stdin.write(build_msg({"jsonrpc": "2.0", "method": "initialized", "params": {}}))
proc.stdin.flush()
time.sleep(0.5)
config_req = read_lsp_message(proc.stdout)
items = config_req["params"]["items"]
print(f"2. ty → client: workspace/configuration (items: {json.dumps(items)})")
if use_fix:
result = [None for _ in items]
label = "correct per LSP spec"
else:
result = []
label = "what Copilot CLI 1.0.6 sends"
print(f"3. client → ty: result: {json.dumps(result)} ({label})")
proc.stdin.write(build_msg({"jsonrpc": "2.0", "id": config_req["id"], "result": result}))
proc.stdin.flush()
time.sleep(2)
code = proc.poll()
if code is not None:
stderr = proc.stderr.read().decode()
print(f"4. CRASH — ty exited with code {code}")
for line in stderr.split("\n"):
if "panic" in line.lower() or "mismatch" in line.lower():
print(f" {line.strip()}")
else:
print("4. OK — ty is still running (no crash)")
proc.terminate()
proc.wait(timeout=3)
import shutil
shutil.rmtree(workspace, ignore_errors=True)
if __name__ == "__main__":
main()
Copilot CLI returns an empty array
[]in response toworkspace/configurationrequests, violating the LSP 3.17 spec. This causes language servers like ty to crash.Steps to reproduce
ty serveras an LSP subprocessinitialize, the client advertisescapabilities.workspace.configuration: trueinitialized, ty sends aworkspace/configurationrequest with 1 item[]instead of[null]Mismatch in number of workspace URLs (1) and configuration results (0)Expected vs actual
[null]nullfor unknown settings[]Versions
LSP 3.17 spec reference
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_configuration
The response array must have the same length as the request's
itemsarray.Repro script
Notes
[null](onenullper requested item) when Copilot CLI has no configuration to provide.