Skip to content

--output json produces invalid JSON: status messages written to stdout instead of stderr #52

Description

@jack-tolley

Description

When using -o json, several commands write human-readable status messages to stdout alongside the JSON payload. This makes stdout invalid JSON, breaking jq, json.load(), and any pipeline that expects clean machine-readable output.

Examples

notes create -o json prepends a success banner:

✓ Note created with ID: 98765
{
  "id": "98765",
  ...
}

contacts search -o json prepends a count line:

Found 3 contact(s)
{
  "results": [...]
}

notes list -o json appends pagination text after the JSON:

{
  "results": [...]
}

More results available. Use --after abc123 to get the next page.

Impact

Any scripting that pipes hspt output through jq or parses it programmatically fails without preprocessing (e.g. tail -n +2 | jq). This defeats the purpose of --output json.

Root cause

In internal/view/view.go, the Success(), Info(), Print(), and Println() methods all write to v.Out (stdout). The JSON() method also writes to v.Out, so status messages and JSON data are interleaved on the same stream.

Suggested fix

Route Success(), Info(), Print(), and Println() to v.Err (stderr):

func (v *View) Success(format string, args ...interface{}) {
    msg := fmt.Sprintf(format, args...)
    fmt.Fprintln(v.Err, color.GreenString("✓ %s", msg))
}

func (v *View) Info(format string, args ...interface{}) {
    msg := fmt.Sprintf(format, args...)
    fmt.Fprintln(v.Err, msg)
}

Error() and Warning() already write to v.Err, so this just makes the pattern consistent. Status messages still appear on the terminal (stderr is displayed by default), but stdout carries only the structured data payload.

This is a ~4-line change in one file with no API breakage. It follows standard Unix convention where structured output goes to stdout and human-readable messages go to stderr.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions