{"record":{"id":"a828d45c1084be1a","repo":"wavetermdev/waveterm","slug":"termref-not-current","errorCode":null,"errorMessage":"TermRef not current","messagePattern":"TermRef not current","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tsunami/app/hooks.go","lineNumber":42,"sourceCode":"func UseVDomRef() *vdom.VDomRef {\n\trc := engine.GetGlobalRenderContext()\n\tval := engine.UseVDomRef(rc)\n\trefVal, ok := val.(*vdom.VDomRef)\n\tif !ok {\n\t\tpanic(\"UseVDomRef hook value is not a ref (possible out of order or conditional hooks)\")\n\t}\n\treturn refVal\n}\n\n// TermRef wraps a VDomRef and implements io.Writer by forwarding writes to the terminal.\ntype TermRef struct {\n\t*vdom.VDomRef\n}\n\n// Write implements io.Writer by sending data to the terminal via TermWrite.\nfunc (tr *TermRef) Write(p []byte) (n int, err error) {\n\tif tr.VDomRef == nil || !tr.VDomRef.HasCurrent.Load() {\n\t\treturn 0, fmt.Errorf(\"TermRef not current\")\n\t}\n\terr = TermWrite(tr.VDomRef, string(p))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn len(p), nil\n}\n\n// TermSize returns the current terminal size, or nil if not yet set.\nfunc (tr *TermRef) TermSize() *vdom.VDomTermSize {\n\tif tr.VDomRef == nil {\n\t\treturn nil\n\t}\n\treturn tr.VDomRef.TermSize\n}\n\n// UseTermRef returns a TermRef that can be passed as a ref to \"wave:term\" elements\n// and also implements io.Writer for writing directly to the terminal.","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/tsunami/app/hooks.go#L24-L60","documentation":"TermRef implements io.Writer by forwarding data to a terminal block via TermWrite. It refuses to write unless the underlying VDomRef has a currently-mounted/active terminal instance (tracked by the HasCurrent atomic flag). Writing to a stale, detached, or not-yet-mounted terminal ref yields this error instead of silently dropping bytes.","triggerScenarios":"Calling TermRef.Write (directly or via fmt.Fprintf/io.WriteString) when tr.VDomRef is nil, or when the vdom ref exists but HasCurrent is false — e.g. before the block's component mounts the ref, after the block is closed/destroyed, or after a re-render replaced the ref.","commonSituations":"Writing terminal output from a background goroutine after the UI block was closed; writing during startup before the component attached; holding a TermRef across a block close/reopen cycle.","solutions":["Check tr.VDomRef != nil and tr.VDomRef.HasCurrent.Load() before writing, and re-acquire a fresh ref if stale","Retry after the component mounts (wait for HasCurrent to become true)","Route writes through a channel/queue drained only while the ref is current","Treat the error as 'terminal gone' and stop writing rather than retrying forever"],"exampleFix":"// before\nn, err := termRef.Write(data)\n// after\nif termRef.VDomRef == nil || !termRef.VDomRef.HasCurrent.Load() {\n    return // terminal not attached; skip or buffer\n}\nn, err := termRef.Write(data)","handlingStrategy":"validation","validationCode":"func canWrite(tr *tsunami.TermRef) bool {\n    return tr != nil && tr.VDomRef != nil && tr.VDomRef.HasCurrent.Load()\n}\nif canWrite(tr) {\n    tr.Write(data)\n}","typeGuard":"func isCurrentTermRef(tr *tsunami.TermRef) bool {\n    return tr != nil && tr.VDomRef != nil && tr.VDomRef.HasCurrent.Load()\n}","tryCatchPattern":"n, err := tr.Write(p)\nif err != nil && err.Error() == \"TermRef not current\" {\n    // buffer or drop; terminal detached\n}","preventionTips":["Always check HasCurrent before writing to a TermRef","Never hold a TermRef across block close/reopen; reacquire it","Marshal writes through a single goroutine that validates currency"],"tags":["go","vdom","terminal","io-writer"],"backgroundTag":"stale-ref-write","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}