{"record":{"id":"53c0dc64fd661ca9","repo":"t8y2/dbx","slug":"handshake-s-w","errorCode":null,"errorMessage":"handshake %s: %w","messagePattern":"handshake (.+?): %w","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"agents/drivers/rabbitmq/bench/agent_compare.go","lineNumber":179,"sourceCode":"\treadySamples := map[string][]float64{}\n\thandshakeSamples := map[string][]float64{}\n\trssSamples := map[string][]int64{}\n\treadyDurations := map[string]time.Duration{}\n\thandshakeDurations := map[string]time.Duration{}\n\tfor iteration := 0; iteration < iterations; iteration++ {\n\t\torder := agents\n\t\tif iteration%2 == 1 {\n\t\t\torder = []agentSpec{agents[1], agents[0]}\n\t\t}\n\t\tfor _, agent := range order {\n\t\t\tprocess, readyDuration, err := startAgent(agent.Command)\n\t\t\tif err != nil {\n\t\t\t\tpanic(fmt.Errorf(\"start %s: %w\", agent.Name, err))\n\t\t\t}\n\t\t\thandshakeStart := time.Now()\n\t\t\tif _, err := process.call(\"handshake\", map[string]any{}); err != nil {\n\t\t\t\tprocess.kill()\n\t\t\t\tpanic(fmt.Errorf(\"handshake %s: %w\", agent.Name, err))\n\t\t\t}\n\t\t\thandshakeDuration := time.Since(handshakeStart)\n\t\t\treadySamples[agent.Name] = append(readySamples[agent.Name], milliseconds(readyDuration))\n\t\t\thandshakeSamples[agent.Name] = append(\n\t\t\t\thandshakeSamples[agent.Name],\n\t\t\t\tmilliseconds(readyDuration+handshakeDuration),\n\t\t\t)\n\t\t\trssSamples[agent.Name] = append(rssSamples[agent.Name], readRSSKB(process.command.Process.Pid))\n\t\t\treadyDurations[agent.Name] += readyDuration\n\t\t\thandshakeDurations[agent.Name] += readyDuration + handshakeDuration\n\t\t\tif err := process.close(); err != nil {\n\t\t\t\tpanic(fmt.Errorf(\"close %s: %w\", agent.Name, err))\n\t\t\t}\n\t\t}\n\t}\n\n\tresults := make([]benchmarkResult, 0, len(agents)*2)\n\tfor _, agent := range agents {","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/rabbitmq/bench/agent_compare.go#L161-L197","documentation":"This panic wraps a failure of the JSON-RPC 'handshake' call issued right after a measured agent startup. Like the warm-up variant, it fires when the agent process died or misbehaved between the ready line and the handshake response: stdout closed early ('agent response unavailable'), response id mismatch, JSON decode error, or an agent-returned JSON-RPC error. The harness panics because a failed handshake invalidates that startup sample and usually indicates a systematically broken agent.","triggerScenarios":"In benchmarkStartups measured iterations: after startAgent() succeeded, process.call(\"handshake\", map[string]any{}) returns an error — child exited after becoming ready, scanner EOF/reader error on the response, unmarshalling failure, response.ID != nextID, or response.Error != nil.","commonSituations":"Agent crashes on first real request (dependency missing, DB/broker connection refused inside agent init); protocol version mismatch after rebuilding one agent (id or envelope format changed); JVM OOM during startup under benchmark load; agent writes extra non-JSON log lines to stdout, corrupting the JSON-RPC stream so unmarshal fails.","solutions":["Inspect agent stderr for the crash/error at handshake time.","Ensure the agent logs only to stderr, never stdout (stdout is the JSON-RPC channel) — stray log lines break JSON decoding.","Rebuild both agents against the same handshake protocol so ids and envelope fields match.","Test the agent standalone: pipe a handshake request to its stdin and confirm a well-formed {\"id\":1,\"result\":...} reply.","Check memory limits; JVM agents can be OOM-killed during startup.","Replace panic with an error return and skip/retry that sample instead of aborting."],"exampleFix":"// before\nif _, err := process.call(\"handshake\", map[string]any{}); err != nil {\n\tprocess.kill()\n\tpanic(fmt.Errorf(\"handshake %s: %w\", agent.Name, err))\n}\n// after\nif _, err := process.call(\"handshake\", map[string]any{}); err != nil {\n\tprocess.kill()\n\treturn nil, fmt.Errorf(\"handshake %s: %w\", agent.Name, err)\n}","handlingStrategy":"try-catch","validationCode":"// After startAgent, confirm the process is still alive before handshaking\nif process.command.ProcessState != nil {\n\tlog.Fatal(\"agent exited before handshake\")\n}\n// And smoke-test handshake offline once:\n// echo '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"handshake\",\"params\":{}}' | <agent>","typeGuard":"func isProtocolError(resp *agentResponse) bool {\n\treturn resp != nil && resp.Error != nil\n}\nfunc idMatches(resp *agentResponse, want int64) bool {\n\treturn resp != nil && resp.ID == want\n}","tryCatchPattern":"func safeHandshake(p *agentProcess, name string) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tp.kill()\n\t\t\terr = fmt.Errorf(\"handshake %s: %v\", name, r)\n\t\t}\n\t}()\n\t_, err = p.call(\"handshake\", map[string]any{})\n\treturn err\n}\n// On error: kill the child, capture stderr, and retry once before failing the run.","preventionTips":["Keep agent stdout strictly JSON-RPC; all logs to stderr.","Version the handshake protocol and assert compatibility at startup.","Retry a failed handshake once with a fresh process before aborting the benchmark.","Watch agent stderr (it inherits to os.Stderr) for the first-crash cause.","Add a bounded timeout around call() so a hung handshake is distinguishable from a crash."],"tags":["go","jsonrpc","handshake","subprocess","panic"],"backgroundTag":"handshake-failed","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}