{"record":{"id":"f08bf5ead5273bad","repo":"wavetermdev/waveterm","slug":"invalid-tabid-or-blockid-passed-to-resynccontrolle","errorCode":null,"errorMessage":"invalid tabId or blockId passed to ResyncController","messagePattern":"invalid tabId or blockId passed to ResyncController","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/blockcontroller/blockcontroller.go","lineNumber":153,"sourceCode":"\t\tEvent:     wps.Event_BlockClose,\n\t\tAllScopes: true,\n\t}, nil)\n}\n\nfunc handleBlockCloseEvent(event *wps.WaveEvent) {\n\tblockId, ok := event.Data.(string)\n\tif !ok {\n\t\tlog.Printf(\"[blockclose] invalid event data type\")\n\t\treturn\n\t}\n\tgo DestroyBlockController(blockId)\n}\n\n// Public API Functions\n\nfunc ResyncController(ctx context.Context, tabId string, blockId string, rtOpts *waveobj.RuntimeOpts, force bool) error {\n\tif tabId == \"\" || blockId == \"\" {\n\t\treturn fmt.Errorf(\"invalid tabId or blockId passed to ResyncController\")\n\t}\n\n\tmu := getBlockResyncMutex(blockId)\n\tmu.Lock()\n\tdefer mu.Unlock()\n\n\tblockData, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting block: %w\", err)\n\t}\n\n\tcontrollerName := blockData.Meta.GetString(waveobj.MetaKey_Controller, \"\")\n\tconnName := blockData.Meta.GetString(waveobj.MetaKey_Connection, \"\")\n\n\t// Get existing controller\n\texisting := getController(blockId)\n\n\t// Check for connection change FIRST - always destroy on conn change","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/blockcontroller/blockcontroller.go#L135-L171","documentation":"ResyncController requires both a non-empty tabId and blockId to locate and resync a block's controller. If either identifier is an empty string, the call is rejected immediately before any locking or DB access. This is a guard against callers passing through missing waveobj IDs.","triggerScenarios":"Calling ResyncController (directly or via ControllerResyncCommand) with tabId=\"\" or blockId=\"\" — e.g. a resync RPC triggered for a block whose IDs were never set, or an event carrying an uninitialized reference.","commonSituations":"Frontend sends a resync message for a block before the block was fully created; deserialized event objects with empty ID fields; iterating blocks where a placeholder row has empty IDs.","solutions":["Check that the blockId and tabId are non-empty before invoking ResyncController","Ensure the block was created via wstore and holds valid IDs before requesting resync","Log the source event to find which caller is sending empty IDs","Use waveobj.MakeORef parsing helpers to validate incoming orefs before extracting IDs"],"exampleFix":"// before\nResyncController(ctx, tabId, \"\", rtOpts, false)\n// after\nif tabId == \"\" || blockId == \"\" { return }\nResyncController(ctx, tabId, blockId, rtOpts, false)","handlingStrategy":"validation","validationCode":"func canResync(tabId, blockId string) bool { return tabId != \"\" && blockId != \"\" }","typeGuard":null,"tryCatchPattern":"if err := ResyncController(ctx, tabId, blockId, rtOpts, force); err != nil && strings.Contains(err.Error(), \"invalid tabId\") {\n    log.Printf(\"skipping resync for incomplete ids\")\n}","preventionTips":["Validate IDs before issuing resync RPCs","Skip placeholder/uninitialized blocks in iteration","Parse orefs and check both IDs are present before dispatching"],"tags":["validation","ids","blockcontroller"],"backgroundTag":"invalid-identifier","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}