can1357/oh-my-pi · error · RuntimeError

Output #{output_id} is not valid JSON: #{e.message}

Error message

Output #{output_id} is not valid JSON: #{e.message}

What it means

`output` with a `query` argument assumes the artifact file contains JSON, because the query is applied to the parsed document. When `JSON.parse(raw)` fails on the artifact's contents, the prelude re-raises with the output ID and the parser's message so the offending artifact can be identified.

Source

Thrown at packages/coding-agent/src/eval/rb/prelude.rb:208

    ids.each do |output_id|
      path = File.join(artifacts_dir, "#{output_id}.md")
      unless File.exist?(path)
        not_found << output_id
        next
      end
      raw = File.read(path, encoding: Encoding::UTF_8)
      raw_lines = raw.split("\n", -1)
      total_lines = raw_lines.length
      selected = raw
      range_info = nil

      if query
        json_value =
          begin
            JSON.parse(raw)
          rescue JSON::ParserError => e
            __omp_emit_status("output", "id" => output_id, "error" => "Not valid JSON: #{e.message}")
            raise "Output #{output_id} is not valid JSON: #{e.message}"
          end
        result_value = __omp_apply_query(json_value, query)
        selected =
          begin
            result_value.nil? ? "null" : JSON.pretty_generate(result_value)
          rescue StandardError
            result_value.to_s
          end
      elsif !offset.nil? || !limit.nil?
        start_line = [offset || 1, 1].max
        if start_line > total_lines
          __omp_emit_status("output", "id" => output_id, "error" => "Offset #{start_line} beyond end (#{total_lines} lines)")
          raise "Offset #{start_line} is beyond end of output (#{total_lines} lines) for #{output_id}"
        end
        effective_limit = limit || (total_lines - start_line + 1)
        end_line = [total_lines, start_line + effective_limit - 1].min
        selected = raw_lines[(start_line - 1)...end_line].join("\n")
        range_info = { "start_line" => start_line, "end_line" => end_line, "total_lines" => total_lines }

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the artifact without `query` first (`output('<id>')`) to inspect its actual content and confirm whether it is JSON.
  2. Fix the upstream step that produced the artifact so it writes valid JSON (e.g. pipe through `jq .` or serialize with JSON.generate).
  3. Use `offset`/`limit` line paging or `format:` instead of `query` for non-JSON artifacts.

Example fix

// before
data = output('result_1', query: '$.items')  # artifact is plain text
// after
raw = output('result_1')                      # inspect first
parsed = JSON.parse(raw) rescue nil
data = parsed ? output('result_1', query: '$.items') : raw
Defensive patterns

Strategy: validation

Validate before calling

raw = output(id)
begin
  JSON.parse(raw)
rescue JSON::ParserError
  # artifact is not JSON — do not use query:
end

Type guard

def valid_json?(str)
  JSON.parse(str)
  true
rescue JSON::ParserError
  false
end

Try / catch

begin
  result = output(id, query: q)
rescue RuntimeError => e
  raise unless e.message.include?('not valid JSON')
  result = output(id) # raw fallback
end

Prevention

When it happens

Trigger: Calling `output('<id>', query: ...)` where `<id>.md` in PI_ARTIFACTS_DIR (or the session-derived artifacts dir) contains plain text, Markdown, truncated JSON, or a trailing newline-joined log rather than a complete JSON document.

Common situations: Pointing query at an artifact that was written as raw command output or Markdown notes; an earlier run crashed mid-write leaving truncated JSON; assuming all artifacts are JSON when only some are.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/f21b1ee0f8e51d75. Report an issue: GitHub.