{"record":{"id":"c3eba555c4cf280d","repo":"siyuan-note/siyuan","slug":"agent-runtime-session-id-mismatch","errorCode":null,"errorMessage":"agent runtime session id mismatch","messagePattern":"agent runtime session id mismatch","errorType":"http","errorClass":null,"httpStatus":200,"severity":"error","filePath":"kernel/agent/runtime.go","lineNumber":207,"sourceCode":"}\n\nfunc loadRuntimeLocked(sessionID string) (*agentRuntime, error) {\n\tdata, err := os.ReadFile(runtimePath(sessionID))\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn &agentRuntime{SchemaVersion: 1, SessionID: sessionID}, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\tvar runtime agentRuntime\n\tif err := gulu.JSON.UnmarshalJSON(data, &runtime); err != nil {\n\t\treturn nil, err\n\t}\n\tif runtime.SchemaVersion > 1 {\n\t\treturn nil, fmt.Errorf(\"unsupported agent runtime schema version: %d\", runtime.SchemaVersion)\n\t}\n\tif runtime.SessionID != \"\" && runtime.SessionID != sessionID {\n\t\treturn nil, fmt.Errorf(\"agent runtime session id mismatch\")\n\t}\n\tif runtime.Revision < 0 {\n\t\treturn nil, fmt.Errorf(\"invalid agent runtime revision\")\n\t}\n\tif runtime.ActiveTurn != nil {\n\t\tif runtime.ActiveTurn.TurnID == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"invalid agent runtime turn id\")\n\t\t}\n\t\tswitch runtime.ActiveTurn.State {\n\t\tcase \"running\", \"finished\", \"interrupted\":\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"invalid agent runtime turn state\")\n\t\t}\n\t}\n\tif runtime.SchemaVersion == 0 {\n\t\truntime.SchemaVersion = 1\n\t}\n\tif runtime.SessionID == \"\" {","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/agent/runtime.go#L189-L225","documentation":"Rejected by siyuan.rpc.unbind(name) when an argument is supplied but is not a string (kernel/plugin/api_rpc.go:109). The same goja.IsString check as bind is applied to the method name; numbers, objects, or undefined-after-optional-chaining values all land here. The lookup key must exactly match the string used at bind time.","triggerScenarios":"unbind(handlerFn) passing the function instead of its name; unbind(3) for an index-built name; unbind(registrations[i]) where the entry is an object like {name, fn}.","commonSituations":"Symmetric APIs in other libraries whose unbind takes the function reference; storing registration records instead of plain names; renaming methods at bind time but unbinding the old symbol.","solutions":["Pass the exact string used in bind: await siyuan.rpc.unbind('myplugin.echo')","If you store records, unbind record.name, not the record","Keep a single constant for each method name used by both bind and unbind"],"exampleFix":"// before\nawait siyuan.rpc.unbind(handler); // function, not name\n\n// after\nawait siyuan.rpc.unbind('myplugin.echo');","handlingStrategy":"type-guard","validationCode":"await siyuan.rpc.unbind(typeof rec === 'string' ? rec : rec.name);","typeGuard":"const isRpcName = (v) => typeof v === 'string' && v.length > 0;","tryCatchPattern":"try { await siyuan.rpc.unbind(value); } catch (e) { if (/first argument must be method name string/.test(e.message)) await siyuan.rpc.unbind(value.name ?? String(value)); else throw e; }","preventionTips":["Unbind by the exact string used at bind time","Store plain name strings, not {name, fn} records, in the unload list","Centralize method names in one constants module"],"tags":["rpc","plugin-api","type-validation","siyuan"],"backgroundTag":"invalid-argument-type","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}