realm/jazzy · warning

[!] Failed: #{full_command}

Error message

[!] Failed: #{full_command}

What it means

jazzy's Jazzy::Executable.execute_command (lib/jazzy/executable.rb) runs external tools (sourcekitten, swift symbolgraph-extract, xcodebuild) via Open4.spawn and captures stdout/stderr. When the child process exits non-zero and the caller passed raise_on_failure = false, it does not raise: it prints "[!] Failed: <command>" to stderr via Kernel#warn and still returns [stdout, status] with the failing status. It is the non-fatal sibling of the `raise "#{full_command}\n\n#{output}"` on the adjacent line, used so a failed attempt can be reported while execution continues. Note a cosmetic quirk: `args.map(&:shellescape)` is an Array that is interpolated but never `join`ed, so the warned message shows the arguments as a Ruby array inspect string (e.g. [!] Failed: /path/to/tool ["doc", "--module", "Foo"]) rather than a copy-pasteable command line.

Source

Thrown at lib/jazzy/executable.rb:41

      def execute_command(executable, args, raise_on_failure, env: {})
        require 'shellwords'
        bin = `which #{executable.to_s.shellescape}`.strip
        raise "Unable to locate the executable `#{executable}`" if bin.empty?

        require 'open4'

        stdout = IO.new
        stderr = IO.new($stderr)

        options = { stdout: stdout, stderr: stderr, status: true }
        status  = Open4.spawn(env, bin, *args, options)
        unless status.success?
          full_command = "#{bin.shellescape} #{args.map(&:shellescape)}"
          output = stdout.to_s << stderr.to_s
          if raise_on_failure
            raise "#{full_command}\n\n#{output}"
          else
            warn("[!] Failed: #{full_command}")
          end
        end
        [stdout.to_s, status]
      end
    end
  end
end

View on GitHub (pinned to b3ee13dd05)

Solutions

  1. Reconstruct and run the warned command manually to see the real error: the child's stderr is already teed to your terminal (stderr = IO.new($stderr)), and the message lists the binary plus the argument array — un-inspect the args and run them; the underlying xcodebuild/sourcekitten/swift failure output tells you what to fix
  2. Fix the arguments or environment for the child tool: correct --scheme/--module/-target values, and for xcodebuild-backed runs add CODE_SIGNING_ALLOWED=NO (and friends) via build_tool_arguments or the env: hash when signing blocks the doc build
  3. Verify the toolchain before invoking: `xcode-select -p` points at full Xcode (not CLT), `swift --version` matches the project, and the tool binary exists on PATH (otherwise you get the separate 'Unable to locate the executable' raise)
  4. If you call execute_command yourself, stop passing false: pass true (as jazzy's own callers at sourcekitten.rb:229 and symbol_graph.rb:28 do) so a non-zero exit raises with the full command plus captured output instead of only warning
  5. When you must keep raise_on_failure = false, check the second element of the returned pair ([stdout, status]) with status.success? / status.exitstatus and handle the failure explicitly instead of relying on the stderr warning

Example fix

# before
stdout, _status = Jazzy::Executable.execute_command(
  'xcodebuild', ['-scheme', 'MyApp', '-destination', 'generic/platform=iOS'], false)
JSON.parse(stdout) # warns "[!] Failed: ..." then crashes on empty stdout

# after
stdout, status = Jazzy::Executable.execute_command(
  'xcodebuild',
  ['-scheme', 'MyApp', '-destination', 'generic/platform=iOS',
   'CODE_SIGNING_ALLOWED=NO'],
  true) # raise on failure: raises "<cmd>\n\n<output>" with the build log
Defensive patterns

Strategy: validation

Validate before calling

require 'mkmf'

# cheap preflight: tool must exist (avoids the sibling
# 'Unable to locate the executable' raise)
raise 'xcodebuild not found' unless find_executable0('xcodebuild')

stdout, status = Jazzy::Executable.execute_command('xcodebuild', args, false)
unless status.success?
  STDERR.puts "doc build failed (exit #{status.exitstatus}); skipping"
  return # never parse `stdout` after a non-zero exit
end
JSON.parse(stdout)

Try / catch

# for the sibling raise_on_failure = true path (executable.rb:39),
# the raise is a plain RuntimeError whose message is
# "<escaped command>\n\n<captured stdout+stderr>":
begin
  Jazzy::Executable.execute_command('swift',
                                    ['symbolgraph-extract', *args], true)
rescue RuntimeError => e
  abort "symbolgraph-extract failed:\n#{e.message}"
end

Prevention

When it happens

Trigger: Calling Jazzy::Executable.execute_command(executable, args, false, env: {...}) — the third positional argument false — when the spawned binary exits non-zero. In this jazzy snapshot all internal callers (sourcekitten.rb:229, symbol_graph.rb:28, symbol_graph.rb:103) pass true, so the warn branch fires for third-party code using Jazzy::Executable directly, or for older/newer jazzy versions that probe or fall back with raise_on_failure: false (e.g. trying xcodebuild vs swift build attempts). Typical failing children: `sourcekitten doc` when xcodebuild cannot build the scheme, `swift symbolgraph-extract` when the module name is wrong or the module cannot be compiled, or any env passed via the env: hash that breaks the build.

Common situations: Running jazzy (or code driving it) where the underlying Xcode toolchain step fails: missing/wrong --scheme or --xcodebuild-arguments so xcodebuild errors; --swift-build-tool symbolgraph without a valid --module; CODE_SIGNING/provisioning failures when building an app target for docs; xcode-select pointing at Command Line Tools instead of full Xcode; a Swift version that cannot compile the module. Also hit by gem users calling Jazzy::Executable.execute_command with false and not realizing the warn is the only signal — the returned stdout is then partial/empty output that later JSON parsing chokes on. Distinct from the earlier raise "Unable to locate the executable" at executable.rb:26, which fires when `which` cannot find the binary at all.


AI-assisted analysis of realm/jazzy@b3ee13dd05 (2026-08-21). Data as JSON: /api/errors/f21025b3ff68c7f4. Report an issue: GitHub.