{"record":{"id":"3c1e5d6b293dde05","repo":"ruby-concurrency/concurrent-ruby","slug":"supported-only-on-root-promise","errorCode":null,"errorMessage":"supported only on root promise","messagePattern":"supported only on root promise","errorType":"exception","errorClass":"Concurrent::PromiseExecutionError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/promise.rb","lineNumber":263,"sourceCode":"    # @return [Promise] a reference to `self`\n    def execute\n      if root?\n        if compare_and_set_state(:pending, :unscheduled)\n          set_pending\n          realize(@promise_body)\n        end\n      else\n        compare_and_set_state(:pending, :unscheduled)\n        @parent.execute\n      end\n      self\n    end\n\n    # @!macro ivar_set_method\n    #\n    # @raise [Concurrent::PromiseExecutionError] if not the root promise\n    def set(value = NULL, &block)\n      raise PromiseExecutionError.new('supported only on root promise') unless root?\n      check_for_block_or_value!(block_given?, value)\n      synchronize do\n        if @state != :unscheduled\n          raise MultipleAssignmentError\n        else\n          @promise_body = block || Proc.new { |result| value }\n        end\n      end\n      execute\n    end\n\n    # @!macro ivar_fail_method\n    #\n    # @raise [Concurrent::PromiseExecutionError] if not the root promise\n    def fail(reason = StandardError.new)\n      set { raise reason }\n    end\n","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/promise.rb#L245-L281","documentation":"Concurrent::Promise#set manually fulfills a promise with a value or block, but only a root promise (one created by Promise.new / Promise.fulfill / Promise.reject) owns its result. A child promise produced by then/on_success/rescue derives its value from its parent's callback, so calling set on it raises PromiseExecutionError 'supported only on root promise' (a StandardError subclass defined in concurrent/promise.rb).","triggerScenarios":"p2 = promise.then { |v| v * 2 }; p2.set(10); calling set on a promise obtained from an aggregate (zip/merge) chain; trying to override or 'correct' a downstream promise's value manually.","commonSituations":"Porting manual-fulfillment code from IVar/Future to Promise chains; attempting to inject a result into a derived step during error recovery; debugging pipelines by forcing values into intermediate nodes.","solutions":["Call set on the root promise only: root = Concurrent::Promise.new; root.set(1)","To supply a child's value, chain it: root.then { |v| 10 } — the callback defines the child's result","For an immediately-fulfilled root use Concurrent::Promise.fulfill(value) or Concurrent::Promise.reject(reason)"],"exampleFix":"# before\nchild = promise.then { |v| v * 2 }\nchild.set(10)\n\n# after\nroot = Concurrent::Promise.new { 5 }\nroot.execute\nroot.then { |v| v * 2 } # child value comes from the parent","handlingStrategy":"validation","validationCode":"root = Concurrent::Promise.new(executor: executor)\nroot.set(value) # only the root may be set\nchild = root.then { |v| v * 2 }","typeGuard":"->(promise) { promise.send(:root?) } # true only for promises created by Promise.new/fulfill/reject","tryCatchPattern":"begin\n  promise.set(value)\nrescue Concurrent::PromiseExecutionError => e\n  raise PromiseMisuse, \"manual set only works on root promises: #{e.message}\"\nend","preventionTips":["Model pipelines so values flow from parents to children via then; never inject into children","Keep a reference to the root promise when a chain needs manual fulfillment","Use Promise.fulfill(value) / Promise.reject(reason) for pre-computed roots"],"tags":["ruby","concurrency","promise","state-machine","api-misuse"],"backgroundTag":"invalid-state-transition","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}