Reference for the rmine CLI (Redmine client) — issue, time-tracking, and project commands, filter/name-matching semantics, and profile handling. Use when the user asks about Redmine issues, tickets, time logging, or projects.
Installs into .claude/skills of the current project.
Are you the author of Cli?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/mehboobali98-cli)
---
name: rmine
description: Reference for the rmine CLI (Redmine client) — issue, time-tracking, and project commands, filter/name-matching semantics, and profile handling. Use when the user asks about Redmine issues, tickets, time logging, or projects.
---
# Reference
Generated by `rmine skill install` — don't hand-edit, rerun the command
instead after upgrading rmine.
## Setup and profiles
`rmine` needs a profile before anything else works: `rmine config init`
(interactive; prompts for server URL + API key, or reads `$RMINE_URL` and
`$RMINE_API_KEY` when set, for unattended setup). Multiple servers: `rmine
config add-profile <name>`, `rmine config use-profile <name>` (persistent
switch), `rmine --profile <name> ...` or `RMINE_PROFILE=<name>` (one-off
override, flag beats env beats the configured current profile), `rmine
config list-profiles`.
## Default project
A profile may carry a default project (`rmine config set-default-project
"X"`), which supplies `--project` wherever it is omitted: on `issue create`,
and as the scope for `issue list` and `time list`.
`time list --issue <id>` is **not** scoped by the default — naming an issue
already determines the project, so the default would only ever contradict it.
**A listing without `--project` may therefore be narrower than it looks.**
When the default takes effect rmine says so on stderr — read that line before
reporting a result as "everything there is". To search across all projects
regardless, pass `--all-projects`; combining it with `--project` is an error.
`rmine config list-profiles` shows whether a default is set.
## Output format
Default output is a human-readable table. Pass `-o json` (long form
`--output json`) on any command when
you intend to parse the result programmatically — always prefer this over
scraping table output.
`-o json` works on **every** command, including the ones that change
something: those return `{"status": "...", "issue": 1234}` (or `time_entry`,
`relation`, `profile`, `path`), so a write can be confirmed by parsing rather
than by matching English. Empty lists come back as `[]`, never `null`. Prompts
and warnings go to stderr, so stdout is always parseable on its own.
**Failures are JSON too.** Under `-o json`, stdout carries
```json
{"error": {"message": "redmine returned 422: Subject cannot be blank", "status": 422, "errors": ["Subject cannot be blank"]}}
```
so every outcome parses — check for an `error` key before reading a result.
`status` and `errors` appear only when Redmine itself rejected the call. That
distinction matters most on `create`: a 422 means nothing was written and
resending with the missing field is safe, while a transport error means the
create may well have landed, so **list before retrying** rather than risking a
duplicate ticket.
## Common request → command mappings
- "my issues due in 2 days" → `rmine issue list --assignee me --due-within 2`
- "issues due next week in project X" → `rmine issue list --project "X" --due-next-week`
- "what's overdue" → `rmine issue list --assignee me --overdue`
- "in progress issues for team X due next week" → `rmine issue list --project "X" --status "in progress" --due-next-week`
- "log 0.5h on ticket 1234" → `rmine time log 1234 --hours 0.5 --activity Development --comment "..."`
- "log 1h of meetings against project X" → `rmine time log --project "X" --hours 1 --activity Development --comment "..."`
- "how much did I work today" → `rmine time list --user me --from <today> --to <today>`
- "create a ticket in project X titled ..." → `rmine issue create --project "X" --subject "..." --tracker Bug`
- "update ticket 1234 to in progress, assign to 42" → `rmine issue update 1234 --status "In Progress" --assignee 42`
- "assign ticket 1234 to Jane" → `rmine issue update 1234 --assignee "Jane"`
- "what is Jane working on in project X" → `rmine issue list --project "X" --assignee "Jane"`
- "close ticket 1234" → `rmine issue close 1234`
- "push ticket 1234's due date to Friday" → `rmine issue update 1234 --due-date 2026-08-28`
- "unassign ticket 1234" → `rmine issue update 1234 --assignee 0`
- "comment on ticket 1234: ..." → `rmine issue comment 1234 "..."`, or `rmine issue comment 1234 --file note.md` for anything multi-line
- "attach this file to ticket 1234" → `rmine issue comment 1234 "..." --attach ./file.pdf`
- "put ticket 1234 in the next sprint" → `rmine issue update 1234 --version "Sprint 42"`
- "what's in Sprint 42" → `rmine issue list --project "X" --version "Sprint 42"`
- "open subtasks of 1234" → `rmine issue list --parent 1234 --status open`
- "open bugs for the EZO team" → `rmine issue list --project "X" --tracker Bug --status open --field 33=EZO` (field ID from `rmine project fields`)
- "1234 has to ship before 1235" → `rmine issue relate 1234 precedes 1235`
- "what are 1234's subtasks" → `rmine issue view 1234 -o json` (read `children`)
## Name matching gotchas
`--project`, `--status`, `--tracker` and `--category` all match
case-insensitively by name (`in progress` finds `In Progress`, `assetsonar
scrum team` finds `AssetSonar Scrum Team`) — no need for exact server casing.
Trackers, statuses, priorities, activities, categories and versions also fall
back to ignoring whitespace, hyphens and underscores when nothing matches
exactly: `Sub-task` finds a tracker named `SubTask`, and `Internal` finds a
category saved as `Internal ` with a trailing space. A loose spelling that
fits more than one name is an error naming them all.
`--assignee` and `time list --user` take a numeric Redmine user ID, the
literal `me`, or a person's **name**.
A name is resolved against the project's member list, so a project must be in
scope: pass `--project` on `issue list` / `time list`; on `issue update` the
issue's own project is used automatically. Without one, rmine says so rather
than guessing — `/users.json` is admin-only on most instances, which is why
the lookup is project-scoped.
An exact name match wins, otherwise a single substring match is used
(`jane` → Jane Doe). A name matching several members is an **error** listing
the candidates — pick one by numeric ID. Never assume which one was meant.
`--assignee 0` unassigns; `--assignee me` on a write resolves to the
authenticated user's ID.
## Due date filters
`--due-within N` (due within the next N days from today), `--due-next-week`
(next Mon–Sun) and `--overdue` (due before today) compute the range for you.
`--due-after` / `--due-before` take explicit `YYYY-MM-DD` dates for a custom
range. The shortcuts and the explicit dates are mutually exclusive with each
other — combining them errors.
Note that `--due-within N` counts forward from today and so **excludes**
anything already late; `--overdue` is the flag for what has been missed.
Every date flag must be `YYYY-MM-DD` and is validated before the request goes
out, so a malformed date is an error rather than an empty result.
## Custom fields
Custom fields differ per Redmine instance (and per project/tracker), and
`--field` takes their numeric ID:
- `--field 12=staging` — repeatable, to set several distinct fields.
- `--field 11=16 --field 11=27` — repeating the **same** ID instead sets
that one field to multiple values, for checkbox/multi-select fields
(Redmine requires an array to set 2+ options).
On `issue list`, `--field id=value` filters instead: repeat an ID to match
any of several values (`--field 33=EZO --field 33=EZR`), and use `*` for
"any value" or `!*` for "not set". Every form, `!*` included, only matches
issues whose tracker has the field: `!*` means "the field applies and was
left empty", so it never counts issues on a tracker without it. The value is
compared as Redmine stores it, so copy it from an issue's `custom_fields` in
`-o json` — a user field holds a user ID, a boolean `1` or `0`.
Find the IDs with `rmine project fields <project>` (`--tracker <name>` for
one tracker). It lists each field's ID, name and the trackers that carry it,
read off the newest issue of each tracker, since `/custom_fields.json` is
admin-only. A tracker with no issue in the project has nothing to read and is
named on stderr and under `unsampled_trackers` in `-o json`.
**A tracker exposes only some of an instance's custom fields, and Redmine
does not reject a write naming one outside that set** — it returns 200 and
drops the value. rmine reads back what the server stored and reports the
difference: a warning on stderr, and `"dropped_fields": [33]` in the `-o json`
output of `create` and `update`. The command still exits 0, because the write
did happen and resending a `create` would file a second ticket.
So: after any write with `--field`, check `dropped_fields`. If it is non-empty
those values are **not** on the issue — set them on a tracker that exposes
them, or tell the user the field is unavailable. Never report a field as set
without looking.
## Previewing a write
Every write (`issue create`, `update`, `close`, `comment`, `relate`,
`unrelate`, `time log`, `edit`, `delete`) takes `--dry-run`. rmine still does
the reads (resolving names, projects and versions exactly as a real run
would), then prints the request it would send instead of sending it, and exits
0. With `-o json` the output is `{"dry_run": true, "requests": [...]}`, each
entry holding `method`, `path` and `body`. An `--attach` shows up as an upload
entry with a `file`, followed by the write that references it.
Use it to show the user the exact change before asking them to approve it,
then run the same command without `--dry-run`. A dry run never prompts, since
it deletes nothing. It also cannot catch a server-side rejection such as a
missing required field, because the write never reaches the server.
## Setting and clearing fields
`issue create` and `issue update` take `--start-date`, `--due-date`,
`--estimated-hours` and `--done-ratio` alongside the usual fields.
On `update`, a flag you don't pass is left untouched on the server, and an
explicitly empty one clears the field:
- `--assignee 0` unassigns, `--parent 0` detaches from the parent
- `--category ""` removes the category, `--estimated-hours 0` drops the estimate
- `--description ""` empties the description
- `--version ""` clears the target version
- `--done-ratio 0` is a real value (0%), **not** a clear
For a long body, write it to a file and pass `--description-file <path>`
(`-` reads stdin) instead of `--description`, on `create` or `update`. It
avoids quoting a Markdown body through the shell; the two flags are mutually
exclusive. The same goes for comments: `--notes-file` on `update`, and
`issue comment <id> --file <path>` in place of the note argument. An empty
comment file is rejected.
`--notes "..."` on `update` records a journal comment **on the same entry as
the field changes**, so the note explains the edit rather than trailing it as
a separate remark. Prefer it over a follow-up `issue comment` when the comment
is about the change you are making.
## Discovering valid values
Every name-matching flag has a command that enumerates what it accepts, so a
`create` can be constructed without sampling existing issues:
| To fill in | Run |
|---|---|
| `--tracker` | `rmine tracker list`, or `rmine project view <project>` for the ones this project accepts |
| `--status` | `rmine status list` (its `CLOSED` column shows which close an issue) |
| `--priority` | `rmine priority list` |
| `--category` | `rmine project categories <project>` |
| `--version` | `rmine project versions <project>` |
| `--activity` | `rmine activity list` |
| `--field` | `rmine project fields <project>` |
`rmine project view <project>` answers several of these at once: it returns
the project's trackers, issue categories and enabled modules alongside the
usual fields. It accepts a numeric ID, an identifier **or** a display name,
as every other project-taking command does.
A name that matches nothing is rejected with the valid spellings listed —
`status: no match for "Doing" (available: New, In Progress, Resolved)` — so a
wrong guess is self-correcting rather than a dead end.
Categories and versions are **project-specific**; trackers, statuses,
priorities and activities are server-wide.
## Target version
`--version` sets Redmine's `fixed_version` on `create` and `update`, takes a
name or a numeric ID, and clears the field when passed `""` on `update`. As a
filter on `issue list` it also accepts `*` (any version) and `!*` (none).
A **name** needs a project in scope to resolve, since version names repeat
across projects; on `issue update` the issue's own project is used.
## Attachments on write
`--attach <path>` (repeatable) uploads a local file and attaches it, on
`issue create`, `issue update` and `issue comment`:
```sh
rmine issue create --project "X" --subject "Q3 report" --attach ./report.pdf
rmine issue comment 1234 "Attaching the spec" --attach ./spec.pdf
```
Each file is uploaded before the write it belongs to, so a rejected write
leaves nothing half-attached. **Don't write "see attached" into a description
until the create that carries the file has succeeded** — that claim is what
`--attach` exists to make true.
## Relations
```sh
rmine issue relate 100 precedes 200 # #100 precedes #200
rmine issue relations 100 # list #100's links
rmine issue unrelate 42 -y # remove relation #42
```
The type reads left to right. Valid types: `relates`, `blocks`, `blocked`,
`precedes`, `follows`, `duplicates`, `duplicated`, `copied_to`, `copied_from`.
`--delay <days>` applies to `precedes`/`follows` only.
`relations` renders each link from the point of view of the issue you asked
about, so the far end of a `precedes` correctly reads as `follows`. `unrelate`
takes the **relation's** ID (the `ID` column of `relations`), not an issue's.
A real dependency belongs in a relation, not in prose in both descriptions.
## Required-field validation
Mandatory fields — standard or custom — vary per Redmine instance and per
project/tracker within one, and there's no reliable way to know them ahead
of time. Don't try to pre-validate: just issue the `create`/`update`, and if
something required is missing the server's error names it exactly (e.g.
`redmine returned 422: Category cannot be blank`). Retry with that field set —
under `-o json` those names are in the `error.errors` array, and a `status` of
422 confirms nothing was written, so the retry cannot duplicate.
## Filter caveats
`--subject` (and combining it with other filters) uses Redmine's advanced
filter syntax under the hood, which does **not** default to open-only —
expect closed issues in results too unless you also pass `--status open`.
`--limit` defaults to 25; pass `--all` to fetch every matching result instead.
`--sort` (on `issue list` and `time list`) takes Redmine's sort syntax: a
column, optionally suffixed `:asc` or `:desc`, comma-separated for tie-breaks
— `--sort due_date`, `--sort "priority:desc,due_date:asc"`. Useful columns:
`due_date`, `priority`, `updated_on`, `created_on`, `status`, `spent_on` (time
entries). Custom fields sort as `cf_<id>`.
## Attachments and comments
`rmine issue view <id>` reports the issue's dates, progress, estimate,
parent, target version and its **`url`** in the Redmine web UI alongside the
usual fields — quote that link when reporting an issue to a person, rather
than the bare number. `issue list -o json` carries `url` per issue too, and always carries its
**attachments** (id, filename, content type, size). **Comments** are extra — a long issue's history
dwarfs the issue itself — so pass `--comments` when you need them. In `-o json`
they land under `attachments` and `journals`; a journal with empty `notes` is a
bare field change, not a comment, and is worth skipping.
`issue view` also returns the issue's **`children`** (the subtask tree, nested,
each with `id`, `tracker` and `subject`) and its **`relations`**. Verifying that
subtasks were parented correctly is one `issue view` on the parent — there is
no need to fetch each child and inspect its `parent.id`.
`rmine issue attachments <id>` lists them on their own. `--download <dir>`
writes every attachment into that directory, creating it if needed:
```sh
rmine issue attachments 54039 --download ./spec
```
Use that when the real content lives in an attached document rather than in the
issue description. A failed download removes its partial file rather than
leaving a truncated one behind.
## Time entries
`rmine time log <issue-id> --hours <n> [--date YYYY-MM-DD] [--activity
<name>] [--comment <text>]` (date defaults to today). For time that is not
attached to a ticket — meetings, planning, support rotations — log against a
project instead: `rmine time log --project "X" --hours 1`. Exactly one of the
issue ID or `--project` is required; passing both is an error. `rmine time list`
(`--issue`, `--project`, `--user`, `--from`, `--to`) also prints a total
across matched entries. `rmine time edit <id>` / `rmine time delete <id>`
(delete prompts unless `-y`/`--force`).
## Commands reference
| Command | Notes |
|---|---|
| `rmine whoami` | Active profile's authenticated user |
| `rmine version` | The running rmine's version |
| `rmine project list` | Browse projects |
| `rmine project view <project>` | One project's details, trackers, categories and enabled modules; takes an ID, identifier or display name |
| `rmine project categories <project>` | List a project's issue categories |
| `rmine project versions <project>` | List a project's target versions |
| `rmine project fields <project>` | Custom field IDs and the trackers that carry them; `--tracker` for one tracker |
| `rmine tracker list` | Trackers defined on this server |
| `rmine status list` | Issue statuses, and which close an issue |
| `rmine priority list` | Issue priorities |
| `rmine activity list` | Time-entry activities |
| `rmine issue list` | `--project`, `--status`, `--assignee`, `--tracker`, `--version`, `--parent`, `--field`, `--subject`, `--updated-after`, `--updated-before`, `--due-after`, `--due-before`, `--due-within`, `--due-next-week`, `--overdue`, `--sort`, `--limit`, `--all`, `--all-projects` |
| `rmine issue view <id>` | Full issue detail, custom fields, web `url`, attachments, `children` and `relations`; `--comments` to also fetch comments |
| `rmine issue attachments <id>` | List attachments; `--download <dir>` saves them all |
| `rmine issue create` | `--project`, `--subject` required; `--description` or `--description-file`, `--tracker`, `--priority`, `--category`, `--assignee`, `--parent`, `--version`, `--start-date`, `--due-date`, `--estimated-hours`, `--done-ratio`, `--field`, `--attach` |
| `rmine issue update <id>` | Same optional flags as create, plus `--status` and `--notes` or `--notes-file` |
| `rmine issue close <id>` | `--status` to pick a specific closed status |
| `rmine issue comment <id> [note]` | Add a comment; `--file` to read it from a file or stdin, `--attach` to include files |
| `rmine issue relations <id>` | List an issue's links to other issues |
| `rmine issue relate <id> <type> <other-id>` | Link two issues; `--delay` for precedes/follows |
| `rmine issue unrelate <relation-id>` | Remove a link; prompts unless `--force` |
| `rmine time log/list/edit/delete` | See above; log takes an issue ID or `--project`, plus `--hours` (required), `--date`, `--activity`, `--comment`; list takes `--issue`, `--project`, `--user`, `--from`, `--to`, `--sort`, `--limit`, `--all`, `--all-projects`; delete prompts unless `--force` |
| `rmine config init/add-profile/use-profile/list-profiles` | Manage server profiles |
| `rmine config set-default-project <project>` | Set the active profile's default project (`""` clears it) |
| `rmine skill install` | (Re)install this skill file (`--local` for the current project, `--force` to overwrite a file rmine didn't write). If a command warns on stderr that this skill was generated by another rmine version, run it: flags documented here may not match the binary |
| `rmine skill uninstall` | Remove it again (same flags) |
Every command accepts `-o`/`--output json` and `--profile <name>`. Every write command accepts `--dry-run`.