{"record":{"id":"0a94a6bdcbb47a19","repo":"clojure/clojure","slug":"can-t-set-s-from-non-binding-thread","errorCode":null,"errorMessage":"Can't set!: %s from non-binding thread","messagePattern":"Can't set!: (.+?) from non-binding thread","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/jvm/clojure/lang/Var.java","lineNumber":225,"sourceCode":"\npublic void setValidator(IFn vf){\n\tif(hasRoot())\n\t\tvalidate(vf, root);\n\tvalidator = vf;\n}\n\npublic Object alter(IFn fn, ISeq args) {\n\tset(fn.applyTo(RT.cons(deref(), args)));\n\treturn this;\n}\n\npublic Object set(Object val){\n\tvalidate(getValidator(), val);\n\tTBox b = getThreadBinding();\n\tif(b != null)\n\t\t{\n\t\tif(Thread.currentThread() != b.thread)\n\t\t\tthrow new IllegalStateException(String.format(\"Can't set!: %s from non-binding thread\", sym));\n\t\treturn (b.val = val);\n\t\t}\n\tthrow new IllegalStateException(String.format(\"Can't change/establish root binding of: %s with set\", sym));\n}\n\npublic Object doSet(Object val)  {\n    return set(val);\n    }\n\npublic Object doReset(Object val)  {\n    bindRoot(val);\n    return val;\n    }\n\npublic void setMeta(IPersistentMap m) {\n    //ensure these basis keys\n    resetMeta(m.assoc(nameKey, sym).assoc(nsKey, ns));\n}","sourceCodeStart":207,"sourceCodeEnd":243,"githubUrl":"https://github.com/clojure/clojure/blob/f3b143341d6efc6428b523b2eaa099a6cc99156e/src/jvm/clojure/lang/Var.java#L207-L243","documentation":"Var.set backs the set! special form on thread-bound dynamic Vars. It throws IllegalStateException when the current thread is not the thread that established the thread-local binding (the TBox records the owning thread), because mutating another thread's binding is undefined behavior in Clojure's binding model.","triggerScenarios":"Calling set! on a thread-bound dynamic var inside a future/agent/thread pool/executor task that runs on a different thread than the (binding [...]) push.","commonSituations":"Setting *out*, *in*, or a custom dynamic var inside a future or core.async thread (:!tp agent threads); logging config vars set in a worker thread; rebinding inside executors.","solutions":["Do the set! on the same thread that created the binding; rebind inside the worker: (binding [*x* v] ...) in the future body","Capture needed values outside the future and pass them as arguments instead of relying on set!","Use an atom (swap!) when cross-thread mutation is the actual requirement","Only use binding/set! for truly per-thread dynamic scope"],"exampleFix":"// before\n(binding [*db* conn]\n  (future (set! *db* other-conn))) ; IllegalStateException\n// after\n(future\n  (binding [*db* other-conn]\n    ...)) ; bind within the worker thread","handlingStrategy":"try-catch","validationCode":"(when-not (= (Thread/currentThread)\n              (:thread (get-in @clojure.lang.Var/dvals [:bindings v])))\n  (throw (ex-info \"not binding thread\" {:var v})))","typeGuard":null,"tryCatchPattern":"(try (set! v val)\n  (catch IllegalStateException e\n    (binding [v val] (do-work))))","preventionTips":["Never set! dynamic vars inside future/agent/executor code","Rebind inside the worker thread instead","Use atoms for cross-thread mutable state"],"tags":["clojure","concurrency","dynamic-binding","threading"],"backgroundTag":"invalid-state-transition","analyzedSha":"f3b143341d6efc6428b523b2eaa099a6cc99156e","analyzedAt":"2026-09-09T12:04:58.961Z","contentChangedAt":"2026-09-09T12:04:58.961Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}