{"record":{"id":"53bdf2f7a740c1d4","repo":"ruby-concurrency/concurrent-ruby","slug":"returned-value-value-inspect-is-not-a-future","errorCode":null,"errorMessage":"returned value #{value.inspect} is not a Future","messagePattern":"returned value #(.+?) is not a Future","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/promises.rb","lineNumber":1907,"sourceCode":"\n      def process_on_blocker_resolution(future, index)\n        countdown = super(future, index)\n        if countdown.nonzero?\n          internal_state = future.internal_state\n\n          unless internal_state.fulfilled?\n            resolve_with internal_state\n            return countdown\n          end\n\n          value = internal_state.value\n          case value\n          when AbstractEventFuture\n            add_delayed_of value\n            value.add_callback_notify_blocked self, nil\n            countdown\n          else\n            evaluate_to(lambda { raise TypeError, \"returned value #{value.inspect} is not a Future\" })\n          end\n        end\n        countdown\n      end\n\n    end\n\n    class RunFuturePromise < AbstractFlatPromise\n\n      private\n\n      def initialize(delayed, blockers_count, default_executor, run_test)\n        super delayed, 1, Future.new(self, default_executor)\n        @RunTest = run_test\n      end\n\n      def process_on_blocker_resolution(future, index)\n        internal_state = future.internal_state","sourceCodeStart":1889,"sourceCodeEnd":1925,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/promises.rb#L1889-L1925","documentation":"When a flattened future resolves, FlatFuturePromise#process_on_blocker_resolution (promises.rb:1899-1908) expects the fulfilled value to be another Future/EventFuture while flattening levels remain. If the value is a plain object — because the chain was flattened one level too many, or flat was used where then was meant — the promise evaluates to a TypeError: \"returned value <inspect> is not a Future\", surfaced by the resulting future (e.g. via value!).","triggerScenarios":"Concurrent::Promises.future { 5 }.flat.value! — the value 5 is not a Future; flat(2) applied to a future nested only one level; a task that usually returns a future but returns a raw value on some code path.","commonSituations":"Using flat for ordinary transformations (then is the correct method); pipeline tasks with inconsistent return types (sometimes a future, sometimes a plain value); computed flatten levels exceeding actual nesting.","solutions":["Use then for plain transformations; reserve flat for tasks whose block returns a Future.","Match the level to real nesting: flat(1) (the default) for a single layer.","Make task returns uniform: wrap plain values with Concurrent::Promises.fulfilled_future(v)."],"exampleFix":"# before\nConcurrent::Promises.future { heavy(5) }.flat.value!\n# -> TypeError: returned value 5 is not a Future\n\n# after\nresult = Concurrent::Promises.future { Concurrent::Promises.future { heavy(5) } }.flat.value!\n# or, for plain transformation:\nresult = Concurrent::Promises.future { heavy(5) }.then { |v| v }.value!","handlingStrategy":"type-guard","validationCode":"f.then { |v| v.is_a?(Concurrent::Promises::Future) ? v.flat : v } # flatten only when actually nested","typeGuard":"def future?(value)\n  value.is_a?(Concurrent::Promises::Future)\nend\n\n# inside a task that sometimes returns raw values:\nresult = compute\nfuture?(result) ? result : Concurrent::Promises.fulfilled_future(result)","tryCatchPattern":"begin\n  result = f.flat.value!\nrescue TypeError => e\n  raise unless e.message.include?('is not a Future')\n  result = f.then { |v| v }.value! # fall back to non-flattening chain\nend","preventionTips":["Use then for plain transformations; use flat only when the task returns a Future.","Keep task return types uniform: always return futures or always return values.","Wrap stray plain values with Concurrent::Promises.fulfilled_future(v).","Match the flat level to the actual nesting depth of your chain."],"tags":["ruby","concurrency","promises","flatmap","typeerror"],"backgroundTag":"flatmap-returns-non-promise","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}