Output Formats
Every command accepts -o / --output to control the output format. The flag is registered globally, so read, write, delete, and every other subcommand honour it uniformly. Pick the one that matches what you’re doing: reading or scripting. For spreadsheets, see CSV export.
Default format, optimised for human reading.
redmine issues list --project myprojectID TRACKER STATUS PRIORITY SUBJECT123 Bug Open High Fix login timeout124 Feature Open Normal Add dark modeMachine-readable output. Ideal for scripting and piping.
redmine issues list --project myproject -o json[ { "id": 123, "tracker": {"id": 1, "name": "Bug"}, "status": {"id": 1, "name": "Open"}, "priority":{"id": 2, "name": "High"}, "subject": "Fix login timeout" }]Filtering with --jq
Section titled “Filtering with --jq”Every command that prints JSON accepts -q / --jq <expression> to filter
its output with a jq expression. No external
jq binary is needed.
# IDs of all open bugs, one per lineredmine issues list --tracker Bug --jq '.[].id'
# A single field of an issueredmine issues get 123 --jq '.status.name'
# Capture the ID of a newly created issueid=$(redmine issues create --project myproject --tracker Bug \ --subject "Crash on save" --jq '.id')
# Count issues by statusredmine issues list --project myproject \ --jq 'group_by(.status.name) | map({status: .[0].status.name, count: length})'
# Filter a raw API responseredmine api /users/current.json --jq '.user.login'How it behaves:
--jqimplies-o json. Combining it with an explicit-o tableis an error.- Commands that never print JSON reject
--jq:completion,mcp serve,mcp tools,attachments download --path -(raw file bytes) and the interactive commands (auth login,auth logout,auth switchwithout a profile name,issues browse,search browse). - The expression runs against exactly the JSON the command would print
without
--jq: resource bodies, lists (an empty list is[]), action envelopes, andredmine apiresponse bodies. - String results are printed raw, without quotes. Numbers, booleans,
null, objects and arrays are printed as JSON. Each result goes on its own line. Whenstdoutis a terminal, control characters in string results are neutralized (see Security hardening); piped output is written byte for byte. - Objects and arrays are compact (one per line) when
stdoutis piped, and indented when it is a terminal. - An empty or invalid expression fails with error code
jq_invalidbefore any request is sent. - A runtime error (for example indexing an object as an array) exits
non-zero with error code
jq_failedand the messagecommand succeeded, but --jq failed: .... The command itself did run: a create, update or delete has already happened, so do not retry it. Fix the expression, or fetch the resource again. The exception isredmine api -ion a failed (4xx/5xx) response: the message is then--jq failed on the error response: ...and the request itself failed. haltandhalt_error(0)stop the filter successfully, keeping any output produced before them; thehalt_error(0)value is not printed.halt_errorwith any other exit code counts as a runtime error (jq_failed, exit code 1), so it can be used as a validation guard.- Output is held back until the expression finishes, so a runtime error
normally leaves
stdoutempty. Once filtered output exceeds 1 MiB it is streamed instead, to keep memory bounded (e.g.--jq 'repeat(1)' | head); in that rare case a later runtime error can leave partial output onstdout. - Errors are never filtered: a failed command writes its error envelope to
stderras-is. - The expression can read the process environment through
envand$ENV. WithREDMINE_JQ_NO_ENV=1, both evaluate to{}instead. SetREDMINE_JQ_NO_ENV=1in wrappers that pass jq expressions from an untrusted source, so they cannot read secrets such asREDMINE_API_KEY.
CSV export
Section titled “CSV export”There is no csv output format. Build CSV from the JSON output with jq’s
@csv instead, which also lets you pick the columns:
# ID and subject of every issue, one CSV row eachredmine issues list --project myproject -o json --jq '.[] | [.id, .subject] | @csv'
# With a header row, nested fields, and all pagesredmine issues list --project myproject --limit 0 -o json \ --jq '["ID", "Status", "Subject"], (.[] | [.id, .status.name, .subject]) | @csv'@csv quotes strings and doubles embedded quotes; numbers are written as
they are and null becomes an empty field. Use @tsv for tab-separated
values. Written to a file or pipe, the values are not altered, so a cell
that starts with =, +, - or @ may be evaluated as a formula when the
file is opened in a spreadsheet app. Rows printed straight to a terminal have
their control characters neutralized (see
Security hardening).
-o csv fails before any request is sent, with a hint to use @csv. A
profile with output_format: csv or REDMINE_OUTPUT_FORMAT=csv falls back
to the table format and prints a warning; a command that picks its format
with -o or --jq uses that format and prints no warning. The value csv
is matched in any letter case.
JSON output shapes
Section titled “JSON output shapes”-o json produces two top-level shapes on stdout:
-
Raw resources for commands that fetch or return data (
issues list,issues get,project create, …). The payload is the resource object (or array of objects) exactly as the API returned it.Collections follow one rule: if a collection was fetched, it is always an array,
[]when empty. If it was not fetched, the key is absent. For exampleissues get 42 --journalsalways hasjournals, even with no history, sojq '.journals[]'is safe. Without--journalsthe key is missing, because an empty array would wrongly say the issue has no history. The same applies to--includesections (children,relations,attachments,trackers,memberships, …), and tocustom_fieldson issues, projects, and users, which are always fetched. One exception: Redmine leaves out collections the account may not see (for examplewatcherswithout the View watchers permission). Those stay absent rather than appearing as[], andissues watchers listfails with a permission hint instead of printing an empty list. -
Action envelope for mutators that have no natural response body (
issues close,issues delete,issues assign,wiki delete,memberships delete, …). The envelope is stable and minimal:{"ok": true,"action": "deleted","resource": "project","id": "demo","message": "Project \"demo\" deleted"}oktells you whether the command changed state. Commands that complete successfully but have nothing to change may return the same envelope withok: falseand a descriptivemessage.The
actionvocabulary is small and intentional:created,updated,deleted,closed,reopened,assigned,unassigned,commented,logged,user_added,user_removed,logged_in,logged_out,switched,installed,opened,uploaded,archived,unarchived,watched,unwatched,related,unrelated.
JSON error envelope
Section titled “JSON error envelope”When -o json (or --jq) is active and a command fails, the CLI writes a
structured error envelope to stderr, leaves stdout empty, and exits
non-zero. The envelope is a single line of compact JSON and always the
last line of stderr, even when warnings or --verbose debug output
come before it:
{"error":{"message":"Resource not found.","code":"not_found"}}code comes from a small stable set: not_found, auth_failed,
forbidden, validation_failed, conflict, server_error, jq_invalid,
jq_failed, status_not_applied, timeout, already_exists,
unknown. Validation and conflict errors include a details
array with the individual messages from Redmine. validation_failed is also
used, without details, when the CLI rejects an argument before sending
anything, such as a project identifier or wiki page title that is not a
single URL path segment (../users/2). jq_invalid means the
--jq expression is empty or malformed and the command did not run;
jq_failed means the --jq expression failed on the command’s output; the
command itself succeeded unless the message says the output was an error
response (see above). timeout means connecting to the server, or waiting
for its response, timed out (see
timeouts);
already_exists means a download target already exists and --clobber was
not given.
status_not_applied means Redmine accepted an issue update but silently kept
the old status (see
status changes are verified).
Its details list the allowed statuses and, when the request carried other
changes, a note that those may already have been applied.
Because the failure never reaches stdout, a pipeline such as
redmine issues create ... -o json | jq '{id}' can no longer mistake an error
for a result. Check the exit code (or set -o pipefail) to detect failures,
and read the last line of stderr for the details:
if ! id=$(redmine issues create --project myproject --tracker Bug \ --subject "Crash on save" --jq '.id' 2>err.log); then tail -n1 err.log | jq -r '.error.code + ": " + .error.message'fiSome errors also carry an optional hint with an actionable next step,
typically a command to run. It is omitted when there is nothing useful to
add; in non-JSON mode the same text is printed on a Hint: line.
{"error":{"message":"Validation error:\n - Category cannot be blank","code":"validation_failed","details":["Category cannot be blank"],"hint":"Required fields and allowed values depend on the project and tracker. Look them up with `redmine projects get myproject --include trackers,issue_categories,issue_custom_fields`, `redmine versions list --project myproject --open`, and `redmine memberships list --project myproject` (assignable users)."}}Hints are currently emitted for:
forbidden: access comes from project roles (or admin status for admin-only endpoints), so retrying with other credentials won’t help. For a single issue, Redmine answers 403 when the issue exists but is private or in a project the user is not a member of.not_foundon a single issue: the issue does not exist on the server.validation_failedon issue writes (issues create,update,assign,close,reopen,comment): commands that list valid values.
Redmine’s messages are localized; branch on code, not on message.
stdout vs stderr
Section titled “stdout vs stderr”- Machine-readable output (JSON, table rows) always goes to
stdout. - Human-oriented messages (success confirmations in non-JSON mode,
warnings, progress spinners, update-check hints) always go to
stderr. - Errors always go to
stderr, asError: ...text or, in JSON mode, as the JSON error envelope. On failurestdoutis empty. - One exception:
redmine apiprints the response of a 4xx or 5xx request tostdout(the body, and with-ithe status and headers) and exits1; with--silentit prints nothing and still exits1. Only in JSON mode without-ior--silentis the failure reported as the error envelope onstderrinstead (see api).
This split is stable — scripts can safely redirect stdout to a file or
pipe it into jq without worrying about interleaved status noise.
Default format
Section titled “Default format”Set a default in your config file:
output_format: tableThe -o flag always overrides the default for a single command, and --jq
always selects JSON.
Security hardening
Section titled “Security hardening”Anyone who can edit an issue, wiki page or name on your Redmine controls part of what the CLI prints. The output layer treats that text as untrusted:
- Terminal output. Tables, detail views, journals, status messages,
errors,
--verbosedebug lines, the status line and headers printed byredmine api -i, and the interactive browsers (issues browse,search browse) neutralize terminal control characters in server data. Escape sequences are shown as visible text (ESC as^[, BEL as^G, a stray carriage return as^M), C1 controls as\u009b-style escapes, and invisible bidi override characters are removed, so a crafted subject cannot set your clipboard, plant hyperlinks, move the cursor or overwrite lines. Regular Unicode (accents, CJK, emoji) is unaffected. JSON,redmine apibodies and attachment downloads are written byte for byte. So are--jqresults whenstdoutis piped or redirected; on a terminal, string results (including@csvand@tsvrows) are neutralized the same way, while JSON results are not. Error messages from Redmine get the same treatment wherever they appear, including thedetailsof JSON error envelopes and MCP tool errors. --jqand the environment.env/$ENVwork by default.REDMINE_JQ_NO_ENV=1hides the environment from the expression.- Opening links.
issues openand the open key in the interactive browsers only launch absolutehttp/httpsURLs, never strings starting with-, and never through a shell (Windows usesrundll32 url.dll,FileProtocolHandler). Issue search results are opened on your configured server. A link to any other host is shown in full in the status line and only opened when you pressoa second time.