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
- 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
- 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
- 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)
- 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
- 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
- Treat "[!] Failed:" on stderr as a real failure, not noise: the command exited non-zero and the returned stdout must not be trusted for parsing
- Always inspect the returned status (second element of [stdout, status]) when passing raise_on_failure = false; prefer passing true in CI so failures raise with the full output
- Dry-run the exact child command (xcodebuild -scheme ..., swift symbolgraph-extract -module-name ...) in a terminal before wiring it into jazzy options
- Pin a healthy toolchain: verify `xcode-select -p` points at full Xcode and the selected Swift version compiles the module you are documenting
- For xcodebuild-backed doc builds of app targets, disable code signing via build_tool_arguments/env (e.g. CODE_SIGNING_ALLOWED=NO) so signing failures cannot fail the docs
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.