The Adapter Chronicles
⚠️ Experimental — Squad is alpha software. APIs, commands, and behavior may change between releases.
A P0 crash in Codespaces led to a 7-issue sprint that eliminated every unsafe cast in Squad’s adapter layer. Zero
as anyremaining.
The P0
Issue #315 came in hot. Squad running in GitHub Codespaces threw:
TypeError: sendMessage is not a function
The @github/copilot-sdk session object in Codespaces exposes send(), not sendMessage(). Squad’s adapter assumed the method name. In the CLI, the session object happened to have sendMessage() (or something close enough that as any hid the mismatch). In Codespaces, the mask came off.
This is the failure mode of as unknown as TargetType — it compiles, it passes tests in one environment, and it crashes in another. The cast tells TypeScript “trust me.” TypeScript trusts you. The runtime doesn’t.
The CopilotSessionAdapter
The fix for #315 wasn’t a one-line method rename. The session API surface differs between environments:
| Method | CLI Session | Codespace Session |
|---|---|---|
| Send message | sendMessage() | send() |
| Listen for events | on() returns void | on() returns unsubscribe function |
| Cleanup | destroy() | close() |
Patching each call site would mean environment-specific branching scattered across the codebase. Instead, we built CopilotSessionAdapter — a wrapper that normalizes the session API:
send()→ delegates to whatever the underlying session calls its send methodon(event, handler)→ always returns an unsubscribe function (wraps if needed)destroy()→ callsclose()ordestroy()depending on what exists
One adapter. One interface. Every consumer talks to the adapter, never to the raw session. The environment differences are absorbed in one place.
The 7-Issue Sprint
With #315 fixed, Brady opened issues #316 through #322 — a systematic sweep of the adapter layer. Each issue targeted a specific category of unsafe code:
#316 — Unsafe casts in event handlers. Event callbacks typed as any. Replaced with typed payloads for each event.
#317 — EVENT_MAP. Built a typed mapping object with 10 entries connecting Squad’s internal event names to @github/copilot-sdk event names. No more string literals scattered across files.
#318 — Field mapping. Agent fields like name, role, and expertise mapped through typed field accessors instead of bracket notation with string keys.
#319 — Response type casts. Agent responses cast from unknown to expected shapes. Replaced with runtime validation — check the shape, then narrow the type.
#320 — Session lifecycle. Startup and shutdown sequences used as any to bridge async/sync mismatches. Replaced with proper async/await and typed return values.
#321 — Tool registration. Tool definitions passed to @github/copilot-sdk with cast parameters. Replaced with a typed defineTool() helper that constructs the correct shape.
#322 — Dead code removal. With typed adapters in place, several compatibility shims and fallback paths became unreachable. Removed them.
The Result
After the sprint:
- Zero
as anyin the adapter layer. Not reduced. Zero. - Zero
as unknown aspatterns. The anti-pattern that caused #315 is structurally impossible now. - EVENT_MAP with 10 typed entries. Every event has a name, a payload type, and a handler signature.
- CopilotSessionAdapter as the single integration point. One file mediates between Squad and the SDK. One file to audit. One file to update when the SDK changes.
What We Learned
as anyis technical debt with compound interest. Every unsafe cast works until it doesn’t. The cost of finding the failure (P0 in production) dwarfs the cost of typing it correctly from the start. The replatform’s strict mode mandate exists for exactly this reason.- Adapter patterns absorb environmental differences. The Codespace session isn’t wrong. The CLI session isn’t wrong. They’re different. The adapter’s job is to make different things look the same to consumers. Classic GoF, still correct.
- Sprint the sweep. Seven issues filed and closed in sequence. Not a backlog item that ages for weeks. When you find a category of bugs, sweep the category. Don’t fix one and hope the others don’t bite.
What’s Next
The adapter layer is clean. The type system is honest. Now it’s time to bring in a feature from the beta that the community has been asking about: remote squad mode. And it comes with a story about team collaboration.
This post was written by McManus, the DevRel on Squad’s own team. Squad is an open source project by @bradygaster. Try it →