canopy-network/canopy · error · Exception

Unexpected handshake response

Error message

Unexpected handshake response

What it means

Thrown in PluginClient.handshake when the FSM's reply contains neither a config nor an error — an out-of-protocol response. It indicates a desynchronized or mismatched FSM (wrong message type, protocol drift, or corrupted transport) where the handshake cannot be interpreted.

Solutions

  1. Confirm plugin and FSM use the same proto message schema and protocol version.
  2. Restart both plugin and FSM to re-establish a synchronized message sequence.
  3. Inspect transport logging for dropped/garbled frames corrupting the response.
Defensive patterns

Strategy: retry

When it happens

Trigger: Thrown at plugin/kotlin/src/main/kotlin/com/canopy/plugin/PluginClient.kt:110 when the library encounters an invalid state.

Common situations: See trigger scenarios.

Understand the failure class


AI-assisted analysis of canopy-network/canopy@ee8197d91d (2026-09-06). Data as JSON: /api/errors/d8e4809cd04e0077. Report an issue: GitHub.

Appendix: source

Thrown at plugin/kotlin/src/main/kotlin/com/canopy/plugin/PluginClient.kt:110

        logger.info { "Handshaking with FSM" }

        val configMsg = PluginToFSM.newBuilder()
            .setId(0)
            .setConfig(ContractConfig.toPluginConfig())
            .build()

        val response = sendSync(null, configMsg)

        when {
            response.hasConfig() -> {
                fsmConfig = response.config
                logger.info { "Handshake complete" }
            }
            response.hasError() -> {
                throw Exception("Handshake failed: ${response.error.msg}")
            }
            else -> {
                throw Exception("Unexpected handshake response")
            }
        }
    }

    /**
     * Read state from FSM
     */
    fun stateRead(contract: Contract, request: PluginStateReadRequest): PluginStateReadResponse {
        val msg = PluginToFSM.newBuilder()
            .setId(contract.fsmId)
            .setStateRead(request)
            .build()

        val response = sendSync(contract, msg)

        return when {
            response.hasStateRead() -> response.stateRead
            response.hasError() -> PluginStateReadResponse.newBuilder()

View on GitHub (pinned to ee8197d91d)