Search

    MENU

    CLI reference

    Markdown

    Use this page for command behavior, shared input rules, and practical examples. For JSON contracts, discovery, and unattended commands, see Scripting and JSON output. For database settings and upgrades, see Settings and Data and backups.

    Contents

    Editing conventions

    Edit commands change only the fields you supply, whereas omitted fields keep their current values. At least one edit field is required. After input normalization, a valid edit that leaves all values unchanged succeeds without writing changes, updating modification timestamps, or recording activity.

    Move, order, archive, unarchive, and task-label attachment commands also succeed without changes when the requested state is already satisfied. This does not apply to creation: creating an existing relation, for example, is an error.

    Edit, move, order, archive, unarchive and task-label attachment commands include changed in their --json results. It is false when the requested state was already satisfied, and a note is printed on stderr. A real change sets changed to true and prints no note. For label attachments, changing any of the requested labels counts as a change.

    Names and titles are trimmed and cannot be blank or contain line breaks or control characters, such as tabs.

    Project, task, and label descriptions may contain multiple lines. Omitted descriptions on creation, empty strings, and whitespace-only strings are stored as null. Note that non-blank descriptions and comment text normalize CRLF line endings to LF, preserving all other whitespace, including lone carriage returns. Use --description '' on an edit to clear a description. JSON returns the full text or null.

    To change part of a description, or of a comment’s text, --append and --replacing modify how --description (or --text on task comment edit) is applied to the stored text:

    			
    				
    						
    						zinn task edit MDR-1 --description 'Bring waffles.' --append
    					
    						
    						zinn task edit MDR-1 --description 'new sentence' --replacing 'old sentence'
    					
    						
    						zinn project edit MDR --description database --replacing board --all
    					
    			
    		

    Flags:

    • --append adds the text as a new paragraph,
    • --replacing <old> replaces the one place where <old> appears, matching exactly and respecting case.
    • --all replaces every match.

    A command with no match, or with several matches and no --all, fails without changing anything. --description '' (or --text '') with --replacing deletes the match. --append and --replacing cannot be combined.

    Append text and both the old and new text in a replacement use the same CRLF-to-LF normalization.

    Descriptions and comment text have a limit of 1 MiB (1,048,576 UTF-8 bytes). Full text is checked before normalization, and partial edits check the resulting text. Exceeding the limit fails without writing changes.

    Text values

    A value that begins with a dash, such as a Markdown bullet, must be joined to its flag with =. Otherwise the command fails without changing anything:

    			
    				
    						
    						zinn task edit MDR-1 --description='- Bring waffles.' --append
    					
    			
    		

    For multi-line text, such as Markdown, read it from a file or from stdin instead. --description-file <path> on project, task and label create and edit, and --text-file <path> on task comment create and edit, take the text from a file. A path of - reads stdin. With a heredoc and a quoted delimiter ('EOF') in bash or zsh, the shell passes $, backticks, quotes and leading dashes as written:

    			
    				
    						
    						zinn task edit MDR-1 --description-file - <<'EOF'
    - Bring waffles.
    - Count the `numbers` for $5 and "Kier".
    EOF
    					
    						
    						zinn task comment create MDR-1 --text-file notes.md
    					
    			
    		
    • A command takes one -, and refuses it when stdin is a terminal.
    • The path must be a regular file or -. A directory or another special file, such as a named pipe, is refused before anything is read.
    • The text must be UTF-8. A leading byte order mark is dropped; other encodings, such as UTF-16, and text containing a NUL character fail.
    • Trailing line breaks are dropped, as $(cat file) does, so the stored text ends with the last character of the last line.
    • Empty or blank input fails. To clear a description, use --description ''.
    • The 1 MiB limit applies to the bytes read, so larger input fails before it is stored.
    • The inline flag and its file flag cannot be combined. --append and --replacing work with either; the --replacing <old> text stays inline.
    • A failed read changes nothing.

    On Windows PowerShell 5.1, pass a file rather than a pipe: piping to a program converts non-ASCII text to ? before Zinn receives it.

    Words and repeated flags

    -- ends the options. Every word after the first -- is considered a plain word, even a secondary instance of -- or anything else that starts with a dash. Flags after it, such as --json, are not read. A trailing -- changes nothing:

    			
    				
    						
    						zinn project column create MDR -- -later
    					
    						
    						zinn task search -- -flag
    					
    			
    		

    Parser and routing error messages are capped at 1,024 UTF-16 units. Short repair commands preserve the original values. Longer repairs give guidance and mark the full command as omitted, so apply the correction to your original input.

    A flag can be given once. Only these flags repeat:

    • --label on task create, task list and task search,
    • --depends-on and --related on task create,
    • --add-label and --remove-label on task edit,
    • --action and --changed on activity list.

    Any other flag given twice, --json and --yes included, fails without changing anything. A help flag is the exception: the first one shows help and the rest of the line is ignored, so -h --help works and help is given whenever needed! No problem!

    Changing tasks in bulk

    task move, task order, task archive, task unarchive and task delete accept multiple task keys, so several tasks can be changed in one command:

    • Tasks are changed in the order given.
    • If any task fails, for example because of an unknown key, none change.
    • A task already in the requested state is unchanged, with a note on stderr. Move, archive and unarchive also accept a repeated key. Order and delete require distinct keys, ignoring case.
    • With --json, one task prints an object and several print an array. Move, order, archive and unarchive include a changed field, which is false when the task was already in the requested state. Delete results omit changed.
    • Delete resolves every key before asking for one confirmation naming all selected tasks. An unknown or repeated key refuses the whole command before confirmation.

    Start a project

    Create a project with a key and optionally provide a name and description:

    			
    				
    						
    						zinn project create MDR --name 'Macrodata Refinement' --description 'Refine macrodata'
    					
    			
    		

    Project keys are stored in uppercase. An omitted name defaults to the key.

    Descriptions print in full with project view. See Editing conventions for name validation and description storage and clearing, and Text values for --description-file.

    Each project starts with Backlog, TODO, Doing, Review, Done and Cancelled columns.

    Every column has a category, which says what its tasks are to Zinn whatever the column is called or where it sits: backlog, todo, in_progress, review, done or cancelled. The default columns take the category of the same meaning. Tasks in done and cancelled columns are closed: they no longer hold back tasks that depend on them. task next hands out tasks from todo columns, then backlog columns. Renaming or moving a column keeps its category.

    You can list projects or inspect a project’s columns with:

    			
    				
    						
    						zinn project list
    					
    						
    						zinn project view MDR
    					
    						
    						zinn project column list MDR
    					
    			
    		

    project list shows each project’s active and archived task counts, including zeros. Active means unarchived, in any column, including Done. Parents and subtasks each count once in their owning project.

    project view shows the project’s description and task counts. Use zinn task list <project-key> to read its tasks.

    project view and project column list accept an omitted project key when defaultProject is configured.

    project column list counts unarchived tasks. With --json, it returns [{ "id": "<column-id>", "name": "Backlog", "is_first": true, "is_last": false, "category": "backlog", "taskCount": 3 }, ...].

    Edit a project:

    			
    				
    						
    						zinn project edit MDR --name 'Macrodata Refinement (Morning Shift)'
    					
    						
    						zinn project edit MDR --description 'Meet the quarterly quota'
    					
    						
    						zinn project edit MDR --description ''
    					
    			
    		

    Provide --name, a description (--description or --description-file), or both. The project key can be omitted when defaultProject is configured. See Text values for values that begin with a dash and multi-line text.

    Add another column at the end of the board, with its category:

    			
    				
    						
    						zinn project column create MDR Boardlog --category backlog
    					
    			
    		

    --category is required. Without it, or with an unknown value, the command fails and lists the valid values.

    Rename a column or change its category:

    			
    				
    						
    						zinn project column edit MDR Doing --name Refining
    					
    						
    						zinn project column edit MDR Shipped --category done --yes
    					
    			
    		

    Provide --name, --category or both. Column names follow the shared name and title rules and match case-insensitively. Names must be unique within the project. Changing only the casing is allowed. Renaming keeps tasks in the column.

    Changing the category of a column that holds tasks, active or archived, asks for confirmation, since it changes which of them count as closed. Use --yes to skip the prompt, which --json and runs without a terminal require. An empty column changes without a prompt.

    Change a column’s position on the board:

    			
    				
    						
    						zinn project column order MDR Review first
    					
    						
    						zinn project column order MDR Review previous
    					
    						
    						zinn project column order MDR Review next
    					
    						
    						zinn project column order MDR Review last
    					
    						
    						zinn project column order MDR Review before Refining
    					
    						
    						zinn project column order MDR Review after Done
    					
    			
    		

    Directions follow board order: first is the start of the board and last is its end. Moving a column to the position it already has, or before or after itself, does nothing. Tasks stay in their columns.

    Delete an empty column:

    			
    				
    						
    						zinn project column delete MDR Review
    					
    			
    		

    Deletion asks for confirmation. Use --yes to skip the prompt, including with --json. The column must hold no active or archived tasks. Use task move or task delete to clear tasks from the column. Archived tasks must be unarchived before moving.

    New tasks enter the first column unless --column flag specifies a different column. A project may have no columns. Add one with project column create before creating tasks.

    zinn project delete MDR asks for confirmation, then permanently deletes the project and all of its columns and tasks. --yes skips confirmation when the project key is typed. With an omitted key and defaultProject, it always asks: --yes is ignored with a warning, and without a terminal the command refuses with exit code 1. With --json, type the key and pass --yes (see Confirmations). Deleting a project releases its key for a future project. See Task key reuse.

    Create and read tasks

    Create a task with a title and an optional description. Titles follow the shared name and title rules. New tasks enter the project’s first column:

    			
    				
    						
    						zinn task create MDR --title 'Refine 75% of the numbers'
    					
    						
    						zinn task create MDR --title 'Review the employee handbook' --description 'Prepare for a 75% Dance Experience'
    					
    			
    		

    The project key can be omitted when defaultProject is configured. --description-file reads the description from a file or stdin (see Text values).

    --column <name> starts the task at the bottom of a specific column. The column name matching is case insensitive:

    			
    				
    						
    						zinn task create MDR --title 'Compile the Lexington letters' --column Doing
    					
    			
    		

    Tasks are created with keys such as MDR-1. Task numbers increase in creation order within each project. List active tasks, optionally limited to one project, or view one task:

    			
    				
    						
    						zinn task list
    					
    						
    						zinn task list MDR
    					
    						
    						zinn task list --all-projects
    					
    						
    						zinn task list MDR --label CLI --label Groom
    					
    						
    						zinn task list MDR --limit 10
    					
    						
    						zinn task list MDR --sort activity --limit 10
    					
    						
    						zinn task list MDR --due-since 2026-10-25T00:00:00Z --sort due
    					
    						
    						zinn task list MDR --column Doing
    					
    						
    						zinn task list MDR --state open
    					
    						
    						zinn task list --column Done --all
    					
    						
    						zinn task view MDR-1
    					
    						
    						zinn task view MDR-1 --history
    					
    			
    		

    task list --state open leaves out tasks in done and cancelled columns, and --state closed lists only those. --state all is the default. A column’s category decides its state, not its name or position. --state combines with every other filter, including --archived, --all and --column.

    task list, task search and activity list accept --limit <n>, a decimal integer from 1 through 9007199254740991. The cap applies after all filters and preserves the existing order. Omit it to return every match. With --json, each command returns an array.

    task list --starting-after <cursor> continues after the last listed task in the selected sort order. The cursor is an opaque token. Copy it from the continuation command that task list prints on stderr when more tasks remain. A task ID is refused.

    Continuation survives edits, moves, archiving, relabelling, deadline changes and deletion of the last listed task. Filters may change while --sort stays the same. A cursor made for another sort or another version is refused with a hint and a suggested task list command. No snapshot is kept between commands, so other tasks changing in between can cause repeats or skips. Ties in the order break by task ID.

    When more matching tasks remain after --limit, stderr gives a shell-quoted continuation command in text and JSON modes.

    task list --sort activity lists tasks by their newest recorded activity, including task changes, comments and relations. Tasks without recorded activity use their update time. Newest comes first, and equal times keep board order. Filters apply before sorting, and --limit caps the sorted result. Without --sort, board order is preserved. Sorting changes only the order in text and JSON.

    task list --sort due lists deadlines from earliest to latest. Tasks without a deadline come last. Equal deadlines and undated tasks keep board order. Sorting does not change task positions. See Task deadlines for filters and input rules.

    Task lists are previews, so they omit descriptions, but task view prints the full description.

    task view includes the task’s creation and update times in local time with a UTC offset. Full task JSON objects include created_at and updated_at as stored integer epoch milliseconds across list, search, view, creation, updates and deletion, including bulk results. Edits, label attachment changes, moves, reorders, archiving and unarchiving advance the update time when they change the task. Comments, relations, column renames, global label-definition edits and operations that lead to no changes leave it untouched.

    An omitted project key uses defaultProject, or every project when none is configured. --all-projects overrides the default.

    task view --history includes the inspected task’s recorded activity, newest first by time, then event ID. Reorders and comment changes are included. Use activity view <activity-id> for full recorded changes. See Activity for timestamps, event IDs, and record fields.

    With --history --json, the task gains an activity array of full activity records. It returns [] when no events are recorded. Without --history, this field is omitted.

    If a project is deleted and recreated with the same key, task view --history shows only the current task’s history. activity list --task <key> --show-deleted also includes history for deleted tasks that previously had that same key.

    Repeat --label <name> to require every specified label. Matching ignores case.

    Use --column <name> to filter by an exact column name, ignoring case, within one project or across projects. Column, label, and archive filters apply together. No matches is a successful empty result ([] with --json).

    Next ready task

    task next shows the one task to work on next:

    			
    				
    						
    						zinn task next MDR
    					
    						
    						zinn task next MDR --column Review
    					
    			
    		

    A task is ready when it is active, in a todo or backlog column, and every task it depends on is archived or in a done or cancelled column. Only dependency relations hold a task back. Tasks in a dependency cycle are never ready.

    Ready tasks in todo columns come before those in backlog columns, so a low-priority todo task comes before an urgent backlog task. Within each, tasks are ordered by priority, urgent first and unset last. Equal priorities go by the earliest deadline, with undated tasks last, and then by the oldest task. Deadlines only order tasks.

    An omitted project key uses defaultProject, or every project when none is configured. --all-projects overrides the default. --column <name> takes ready tasks from one column instead, with the same matching as task list --column. It can name a column of any category but done and cancelled, such as Review.

    When no task is ready, the command still exits 0. With --json, it prints the task object, or null when no task is ready.

    Two callers can receive the same task. To take it, move it with task move --from, which fails with a conflict error if another caller moved it first. Then ask task next again.

    Task blame

    			
    				
    						
    						zinn task blame MDR-1
    					
    						
    						zinn task blame MDR-1 --json
    					
    			
    		

    task blame <task-key> attributes the current title, column, priority, deadline, label membership, parent, relations and description lines to recorded activity. It reads active and archived tasks. A reused key selects its current task lifetime. Unknown and deleted keys produce the ordinary missing-task error. For a worked example of the output, see Task blame.

    Creation records can supply the initial attribution, including unset values. Later edits, moves and membership changes replace it. Labels are one collection, attributed to the latest attachment or removal, including removal when a label is deleted, when the recorded creation and changes add up to the current number of labels. Column and label renames preserve membership attribution while returning the current names. Parent assignment and removal follow outgoing subtask relation events. Each current relation carries its own creation attribution, including incoming links and the parent link.

    Description comparison matches whole lines exactly, including whitespace. Unchanged lines carry their attribution, inserted or replaced lines belong to the introducing edit, and deleted lines disappear. Blank lines are preserved. A terminal newline ends the preceding line without adding another line, and changing only that newline preserves attribution. Null and empty descriptions have no lines. Matching keeps the shared lines at the start and the end in place, then matches the longest sequence of remaining lines that both versions share in the same order. When a comparison could be read more than one way, it treats a line as removed before treating it as added. Each edit has a fixed search budget, enough for about 2,500 replaced lines. Past it, lines not yet matched belong to that edit, so a heavily reordered description reads as rewritten.

    Attribution is null when the required history is absent or unrecognized. Text calls it unrecorded. A scalar’s latest recorded new value must agree with its current value. Description edits must join the previous recorded version before they can carry its attribution. A gap starts with unattributed old lines and attributes only inserted lines to the next edit. A later description record without both the old and new text discards the carried line attribution, so later edits cannot inherit it. If the final recorded description differs from the current text, all current lines are unattributed.

    Recorded events retain the stored actor name, kind and identity fields, with null for absent metadata. Missing actor identity is separate from missing event attribution. Event IDs open with zinn activity view <activity-id>. Blame reads task content and history without recording activity.

    With --json, the command returns the TaskBlame result.

    Search tasks

    Search task titles, descriptions, and comments, optionally in one project:

    			
    				
    						
    						zinn task search refine
    					
    						
    						zinn task search refine --all-projects
    					
    						
    						zinn task search 'quarterly quota' MDR
    					
    						
    						zinn task search MDR-1
    					
    						
    						zinn task search auth --all --limit 5
    					
    						
    						zinn task search auth MDR --label cli
    					
    						
    						zinn task search auth MDR --column Review
    					
    						
    						zinn task search refinement --due-until 2026-10-25T17:00:00+02:00
    					
    			
    		

    Each query matches the start of a word, ignoring case and accents, so cafe finds “Café” and auth finds “Authentication”. Every word must match somewhere in the task: its title, its description, or any of its comments. A comment match lists the task the comment belongs to.

    The query comes first, then the optional project key. An omitted key uses defaultProject, or every project when none is configured. --all-projects overrides the default.

    If the project key does not exist, the error includes the search usage line to clarify the argument order. With --json, this guidance is in error.help.

    If the whole query is an existing task key, such as mdr-1, that task comes first. Other results follow by relevance, with title matches weighted highest. Equally relevant tasks are ordered by project key, then task number.

    An exact task-key query overrides the configured default project. A project key supplied after the query still filters the search.

    Search defaults to active tasks. --archived searches archived tasks and --all searches both. A note on stderr reports hidden archived matches. --limit <n> caps the results.

    --label works as it does for task list: repeat it to require every specified label. --column matches an exact column name, ignoring case, in the selected project or across projects. Label and column filtering happen before --limit and before archived matches are counted.

    With --json, search returns an array of tasks like task list does. A search with no results succeeds. A query that begins with a dash goes after -- (see Words and repeated flags).

    Search uses an index that Zinn keeps up to date as tasks and comments change. zinn reindex rebuilds it from the tasks and their comments, archived tasks included:

    			
    				
    						
    						zinn reindex
    					
    			
    		

    Only the index changes, completely or not at all, so no backup is saved. With --json, it prints { "databasePath": "...", "taskCount": 12 }. The command fails without changing anything when there is no database yet, or when Zinn refuses changes to the database, such as one with a newer schema version (see Upgrade the database).

    Labels

    Labels are global and reusable across projects.

    			
    				
    						
    						zinn label create urgent --color red --description 'Needs attention soon'
    					
    						
    						zinn label create review
    					
    						
    						zinn label edit review --color green
    					
    						
    						zinn label list
    					
    						
    						zinn label view urgent
    					
    			
    		

    Adding to tasks

    			
    				
    						
    						zinn task create MDR --title 'Check the numbers' --label urgent --label review
    					
    						
    						zinn task label add MDR-1 urgent review
    					
    						
    						zinn task label remove MDR-1 review
    					
    						
    						zinn task edit MDR-1 --add-label review --remove-label urgent
    					
    			
    		

    Repeat --label, --add-label, or --remove-label for several labels. Each value is one name, so commas are part of the name. Quote names containing spaces: --label 'needs review'.

    Label names follow the shared name and title rules, preserve their stored casing, and match case-insensitively. Changing only the casing is allowed; names that differ only in case cannot coexist.

    Label colors default to white. Available colors are red, green, yellow, blue, magenta, cyan, white, and gray.

    --json includes each label’s color as a value.

    label edit accepts name, color, and description. label create and label edit take --description-file for a description from a file or stdin (see Text values). Renaming preserves every task attachment because attachments refer to the label’s internal ID. Updating a label definition does not affect attached tasks’ timestamps.

    label create, label edit, label list, and label delete show a preview of the descriptions. label view <name> shows the full description, which preserves line breaks and whitespace. label view --json returns one object with id, name, color, and the description in full.

    Task creation and edits apply content and label changes together, or roll back together on failure. Changing a task’s label attachments updates that task’s modification timestamp. Archived tasks can also have their labels edited.

    --json lists a task’s labels as names in labels, with their IDs in label_ids in the same order. See Entity identity for the ID fields on task and nested results.

    zinn label delete urgent asks for confirmation, then deletes the definition and removes it from every task. Declining confirmation leaves the label and its attachments unchanged. --yes skips the confirmation (see Confirmations). Use task label remove to remove a label attachment from one task instead. Task/project deletion cleans up label attachments, but does not affect labels themselves.

    Task relations

    			
    				
    						
    						zinn task relation create MDR-1 MDR-2 --type dependency
    					
    						
    						zinn task relation create MDR-1 MDR-2 --type duplicate --yes
    					
    						
    						zinn task relation create MDR-1 OTHER-1 --type related
    					
    						
    						zinn task relation list MDR-1
    					
    			
    		

    dependency means the first task depends on the second; duplicate means the first is a duplicate of the second. related is symmetric.

    Each task pair may have one relation across all types and both directions. Changing the type or direction asks for confirmation to replace the existing links. --yes authorizes replacement without a prompt, including with --json or without a terminal. Creating the exact existing relation is an error, including with --yes.

    One confirmation names every link to remove. If those links change while the prompt is open, the command fails and asks you to run it again. Replacement applies completely or not at all, and records the removed links and the new link in activity with one timestamp.

    Relations do not automatically move, complete, or archive tasks. Tasks from separate projects can be related to each other. Tasks can also be related to archived tasks.

    JSON relation arrays from task relation list and task view are ordered by creation timestamp ascending, then relation ID ascending for ties.

    Use zinn task relation create APP-2 APP-1 --type subtask to make APP-2 a child of APP-1. Each task can have one parent and any number of subtasks, including nested subtasks.

    Assigning a different parent asks to replace the previous parent link and any conflicting link to the new parent. Moving or archiving a parent does not change its children, and deleting a parent removes its child relations while retaining child task data.

    task create can relate the new task with --parent, --depends-on, --duplicate-of and --related. Repeat --depends-on and --related to relate several tasks:

    			
    				
    						
    						zinn task create MDR --title 'Sort the files' --parent MDR-1 --depends-on MDR-2
    					
    			
    		

    Repeating the same relation type and target counts once, ignoring task-key case. Different relation types for the same target abort creation without saving the task, consuming its number, or recording activity.

    Remove a relation with zinn task relation delete MDR-1 MDR-2 --type dependency, using its original direction for dependency, duplicate, and subtask. For related, order does not matter. Relation deletion requires no confirmation.

    Task comments

    Add and list comments on a task:

    			
    				
    						
    						zinn task comment create MDR-1 --text 'Ready for review'
    					
    						
    						zinn task comment list MDR-1
    					
    			
    		

    Comments receive keys per task like MDR-1#1.

    After creating or editing a comment, the text echo is limited to its first line and 80 terminal columns, including an ellipsis when text is omitted. Emoji and combining characters stay intact. --json keeps the full text.

    View, edit or delete a comment using its key:

    			
    				
    						
    						zinn task comment view 'MDR-1#1'
    					
    						
    						zinn task comment edit 'MDR-1#1' --text Approved
    					
    						
    						zinn task comment delete 'MDR-1#1'
    					
    			
    		

    task comment create and task comment edit take --text-file instead of --text to read the text from a file or stdin (see Text values).

    task comment view shows the current text in full, keeping its line breaks, including on archived tasks. With --json, it returns one object with id, key, task_id, text, created_at and updated_at.

    Deletion asks for confirmation, unless --yes is used (see Confirmations). Comments can be added to archived tasks. Comment mutations are recorded in the activity log without changing the task’s modification timestamp.

    Edit tasks

    Edit a task’s title, description, priority, or labels:

    			
    				
    						
    						zinn task edit MDR-1 --title 'Meet the quarterly refinement quota'
    					
    						
    						zinn task edit MDR-1 --description 'Complete refinement before the waffle party'
    					
    						
    						zinn task edit MDR-1 --title 'Meet the quarterly refinement quota' --description ''
    					
    			
    		

    See Editing conventions for title validation, partial edits and clearing descriptions.

    Archived tasks can be edited without unarchiving them. See Text values for values that begin with a dash and multi-line text.

    Move and order tasks

    Move a task to another column in its project:

    			
    				
    						
    						zinn task move MDR-1 Doing
    					
    			
    		

    A moved task appears at the bottom of its destination column. Moving it to its current column does nothing. Archived tasks must be unarchived before they can be moved.

    For multiple tasks, give their keys before the column. They move in input order; tasks already in that column retain their positions. Each task’s column is looked up in its own project:

    			
    				
    						
    						zinn task move MDR-1 MDR-2 APP-4 Doing
    					
    			
    		

    See Changing tasks in bulk.

    --from moves a task only if it is still in the given column. Use it to take a task that another agent or person may be taking at the same time:

    			
    				
    						
    						zinn task move MDR-1 Doing --from TODO
    					
    			
    		

    When the task has left that column, the move changes nothing and fails with error code conflict and reason column_changed. The error’s expectedColumn and actualColumn name both columns. This holds even when the task is already in the target column, so of several callers taking the same task, exactly one succeeds. With several task keys, one task outside the column fails the whole move.

    Change a task’s position within its current column:

    			
    				
    						
    						zinn task order MDR-2 top
    					
    						
    						zinn task order MDR-2 up
    					
    						
    						zinn task order MDR-2 down
    					
    						
    						zinn task order MDR-2 bottom
    					
    						
    						zinn task order MDR-2 before MDR-1
    					
    						
    						zinn task order MDR-2 after MDR-1
    					
    						
    						zinn task order MDR-2 MDR-3 top
    					
    			
    		

    The task being reordered and the target of before or after must be active tasks in the same column.

    For several tasks, ordering is the same as ordering the first task as requested, then each next task after the one before it. Tasks form a block in the given order. Each step that already satisfies its placement leaves that task unchanged and records no activity and other tasks retain their relative order. All source tasks must be distinct active tasks in the same project and column, and the target cannot be among them. up and down take one task.

    Archive and delete tasks

    Active tasks are shown by default. Archive one or more tasks to hide them from active lists, then list archived tasks or all tasks:

    			
    				
    						
    						zinn task archive MDR-1
    					
    						
    						zinn task archive MDR-2 MDR-3
    					
    						
    						zinn task list MDR --archived
    					
    						
    						zinn task list MDR --all
    					
    			
    		

    --archived and --all cannot be used together.

    Unarchiving puts a task at the bottom of its previous column:

    			
    				
    						
    						zinn task unarchive MDR-1
    					
    						
    						zinn task unarchive MDR-2 MDR-3
    					
    			
    		

    Archive or unarchive multiple tasks by giving several keys. Unarchived tasks land in the order given. See Changing tasks in bulk.

    Delete tasks permanently with zinn task delete MDR-1 MDR-2. Active and archived tasks from any project can be deleted together. It asks for one confirmation before performing the action, unless --yes is used (see Confirmations). See Changing tasks in bulk for validation and JSON results. See Task key reuse for what happens to task numbers.

    Task priority

    Priority is optional: low, medium, high, or urgent. Priority values, including none, are case-insensitive. Stored values and JSON use lowercase. New tasks have no assigned priority by default.

    Text priority markers are - (unassigned), L (low), M (medium), H (high), and U (urgent).

    			
    				
    						
    						zinn task create MDR --title 'Review the numbers' --priority high
    					
    						
    						zinn task edit MDR-1 --priority urgent
    					
    						
    						zinn task edit MDR-1 --priority none
    					
    			
    		

    Use none to clear priority. Priority does not affect board ordering.

    Task deadlines

    Set one exact deadline when creating or editing a task:

    			
    				
    						
    						zinn task create MDR --title 'Review the refinement report' --due 2026-10-25T15:30:00.123Z
    					
    						
    						zinn task list MDR --due-since 2026-10-25T00:00:00Z --due-until 2026-10-25T18:00:00+02:00 --sort due
    					
    						
    						zinn task search refinement MDR --due-until 2026-10-25T18:00:00+02:00
    					
    						
    						zinn task edit MDR-1 --due none
    					
    			
    		

    --due accepts integer Unix epoch milliseconds or a complete timestamp with seconds and an explicit Z or numeric UTC offset, such as 2026-10-25T17:00:00+02:00. Fractional seconds may have one to three digits. Integer values range from -8640000000000000 to 8640000000000000, inclusive. Date-only values, timestamps without an offset, relative dates, impossible dates, leap seconds, 24:00, and fractions longer than three digits are rejected.

    Use --due none to clear a deadline. The word is case-insensitive. Omitting --due leaves a new task undated and leaves an edited task unchanged. Repeating the current deadline or clearing an unset deadline does not update the task or record activity.

    --due-since and --due-until select deadlines inclusively on task list and search. Use either bound alone or both together. Equal bounds select that exact millisecond. An upper bound before the lower bound is invalid. Undated tasks do not match a supplied bound. Deadline bounds intersect the other filters and apply before --limit. Search keeps its existing relevance order, including task-key matches. Due sorting is available on task list only. It does not change board order or the default list and search order.

    Task text shows deadlines in local time with a UTC offset, seconds, and any nonzero milliseconds. Full task JSON objects include due_at as integer epoch milliseconds or null when unset. Activity text uses the same precision for deadline changes. Event timestamps keep their existing precision.

    Activity

    Zinn records changes to tasks, projects, columns, labels, relations, and comments, including who made them.

    Deleting a project records one project deletion rather than separate deletion activity for every cascaded row. Each removed cross-project relation records relation_delete activity on its surviving task, including archived tasks, without changing that task’s updated_at.

    			
    				
    						
    						zinn activity list
    					
    						
    						zinn activity list --limit 10
    					
    						
    						zinn activity list --since 2026-10-25T00:00:00Z --until 2026-10-25T12:00:00Z
    					
    						
    						zinn activity list --project MDR
    					
    						
    						zinn activity list --project MDR --show-deleted
    					
    						
    						zinn activity list --project-id 019abcdf-1234-7000-8000-123456abcdef
    					
    						
    						zinn activity list --task MDR-1
    					
    						
    						zinn activity list --task MDR-1 --show-deleted
    					
    						
    						zinn activity list --task-id 019abcdf-1234-7000-8000-123456abcdef
    					
    						
    						zinn activity list --session conversation-id
    					
    						
    						zinn activity list --client zinn-opencode:panel
    					
    						
    						zinn activity list --session conversation-id --thread child-thread-id
    					
    						
    						zinn activity list --action comment_create --action comment_edit
    					
    						
    						zinn activity list --action edit --changed description
    					
    						
    						zinn activity list --task MDR-1 --changed due_at
    					
    						
    						zinn activity list --changed column=Review
    					
    						
    						zinn activity list --changed priority=high --changed labels=bug
    					
    						
    						zinn activity list --changed column=Review --changed column=Done --limit 10
    					
    						
    						zinn activity list --follow --changed column=Review
    					
    						
    						zinn activity view 019abcdf-1234-7
    					
    			
    		

    --limit caps the newest matching events and must be an integer from 1 through 9007199254740991. Reorders and both affected-task events from relation changes are included.

    Use --starting-after <activity-id> to continue toward older events, while excluding the cursor event. It accepts the same full IDs and unique prefixes as activity view. The cursor event need not match the filters. Keep the same filters when continuing. Without --limit, the command returns every remaining match.

    When another matching event exists after a capped page, stderr gives a command to continue with the last returned event’s full ID. The command retains the filters and output mode. This hint also appears with --json, whose stdout remains an array of full activity records. Scripts can continue with the last event’s ID whenever a page contains exactly the requested limit. An empty array marks exhaustion.

    --follow prints activity as any client commits it and runs until stopped. It prints only events committed after it starts, oldest first in commit order. The other filters apply to followed activity as they do to a listing. --follow cannot be used with --limit or --starting-after.

    Ctrl-C ends a following session. It also ends successfully once the reader of its output has closed it, as in zinn activity list --follow | head -1, when it writes the next event. It ends with a conflict error that names the database when its schema changes, including a restore, migration or reindex, or when its file is replaced or removed. Run the command again to follow from that point.

    --since and --until select events on or after the lower bound and on or before the upper bound, using their recorded created_at. Use either flag alone or both together. Equal bounds select events at exactly that millisecond. --until must be at or after --since. All supplied filters must match before --limit applies.

    Time values accept integer epoch milliseconds or YYYY-MM-DDTHH:mm:ss followed by Z for UTC or a numeric offset such as +01:00. Optional fractional seconds have 1 to 3 digits, so .1, .12 and .123 mean 100, 120 and 123 milliseconds. For example, 2026-10-25T12:00:00.123Z and 2026-10-25T13:00:00.123+01:00 select the same instant on every computer. Integer inputs always mean milliseconds and must be between -8640000000000000 and 8640000000000000, inclusive. Use --since=-1 for a negative value.

    This is a bounded subset of RFC 3339. Times require seconds and an explicit offset. Impossible dates, leap seconds, 24:00 and fractions longer than 3 digits are rejected.

    --task selects the existing active or archived task by readable key. If that task is missing, the command fails and points to --show-deleted.

    --project selects the existing project’s activity, including its columns and active, archived, and deleted tasks. If that project is missing, the command fails and points to --show-deleted. Task comments, label attachments, and reorders are included. Global label-definition events are excluded. A relation between projects includes only the selected project’s task event; a relation within the project includes both task events.

    Add --show-deleted to a readable-key selector to include every recorded existing or deleted identity sharing that key. With --project, this expands project identities; explicitly deleted tasks are already included for each selected project.

    --task-id and --project-id each select one exact identity, including after deletion, using a full case-insensitive UUID in canonical hyphenated or compact 32-hex form. ID prefixes are rejected. --project, --project-id, --task, and --task-id are mutually exclusive; --show-deleted requires --project or --task.

    Deleted history and exact ID selection include the original project-deletion event when its snapshot contains the selected task. A valid exact ID or history query without recorded matches returns an empty list.

    --session and --thread match the complete stored IDs exactly, including case and whitespace. All supplied filters must match, and --limit applies after filtering. Rows without the requested identity do not match. No matches return empty output, or [] with --json, and exit successfully. Session activity records committed changes.

    --client <name> matches a client anywhere in the recorded chain, including case. Zinn splits the chain on whitespace and takes each token’s name before its first /. A name matches exactly or when followed by :, so --client zinn-opencode selects zinn-opencode/0.3.0 and zinn-opencode:panel/0.3.0. Use --client zinn-opencode:panel to select the panel. --client zinn does not match zinn-opencode. Other filters must also match before the limit applies.

    --action <action> matches the recorded action, such as edit, move, comment_create, comment_edit or comment_delete. Action names use the same spelling and case as activity JSON. Repeat --action to match any listed action. Action and field filters must match the same event.

    --changed <field> matches a change to a task’s column, priority, labels, title, description or due_at. Labels match additions and removals. Initial task creation fields also match, including an unassigned priority or description and an unset deadline. Initial labels match when at least one label is recorded. Use --action edit --changed description to select description edits. due_at matches any deadline change and does not take a value.

    --changed <field>=<value> matches a recorded change to column, priority, or labels, ignoring value case. Column and priority match their new values. Labels match an added label. Initial values on task creation also match. Use --changed priority=none for creation without a priority or a change that clears it. Column and label names match as recorded in the event, including after renaming or deletion.

    Repeat --changed for several filters. Filters for the same field match any of them, including a value-free filter alongside one with a value. For example, --changed column=Review --changed column=Done selects events entering either column. Different fields must match the same event, so --changed priority=high --changed labels=bug selects events that both set high priority and add bug. Other supplied filters must also match, before --limit applies. Unknown fields fail and name the accepted fields.

    Task creation snapshots include due_at as an integer epoch-millisecond value or null. A deadline edit records its previous and new values, with null for an unset deadline. Older activity payloads keep their recorded shape.

    Activity text uses local dates and times with a UTC offset. activity list uses the shortest globally unique left prefixes of at least eight hexadecimal digits for event IDs.

    activity view accepts a full UUID or a unique prefix of at least eight hexadecimal digits for convenience. The match ignores case and accepts both the full hyphenated or compact forms. Prefixes must be unique across all activity. An ambiguous prefix lists candidate IDs and requires more characters. Use a full ID for reliable reference results. Using prefixes may result in unreliable results over time as history grows.

    activity view preserves full recorded text and line breaks, including unknown change fields.

    With --json, list returns an array of activity records and view returns one. With --follow --json, list prints each record as one JSON object on its own line.

    With --show-deleted, readable keys can include different identities after a project is deleted and recreated. Group JSON records by (entity_type, entity_id) to keep their histories distinct, or use --task-id or --project-id for one identity.

    Hooks receive these same records on stdin, with the task’s key added as task_key. See Hooks.

    Who am I?

    Run zinn whoami to see the actor a change would record in the current environment and folder, including the source of its name and kind:

    			
    				
    						
    						zinn whoami
    					
    						
    						zinn whoami --json
    					
    			
    		

    For the human fallback, Git identity comes from the current folder. The command works without reading settings or opening storage, including when the store is missing or unusable. It shows the resolved name, email, kind, source, session, thread, agent, model, effort and client. An unnamed agent is shown as unknown.

    --json returns one object with actor_name, actor_email, actor_kind, actor_source, actor_session_id, actor_thread_id, actor_agent, actor_model, actor_effort and actor_client, matching the actor fields of an activity record. Missing values are null.

    Each mutation records the actor identity supplied by the CLI. Zinn checks AI_AGENT and AGENT declarations before known tool markers for Claude Code, Codex, OpenCode, Gemini CLI, Cline, Cursor, Copilot’s coding agent, and Crush. Set AI_AGENT to name another tool. Numeric-only and boolean-word values for AI_AGENT or AGENT are treated as flags, not names, and do not prevent a later marker from identifying the agent. If only a generic flag is set, the actor kind is agent and its name is null.

    With no agent marker, Zinn uses your git user.name and user.email, then your system username. actor_source records the deciding env:<VARIABLE>, git-config or os-user.

    Integrations can supply ZINN_SESSION_ID and ZINN_THREAD_ID. When either is set, Zinn uses that pair. Otherwise it uses the first available pair: CLAUDE_CODE_SESSION_ID with a null thread, CODEX_SESSION_ID with CODEX_THREAD_ID, or OpenCode’s OPENCODE_SESSION_ID as the thread with a null session. If a member is missing, it stays null; Zinn does not combine values from different pairs.

    ZINN_AGENT, ZINN_MODEL and ZINN_EFFORT declare the host agent name, running model and effort for the execution making the change. Zinn trims surrounding whitespace, treats blank declarations as unset and preserves other text. These values are declarations rather than authenticated identities. Activity JSON keeps these values with the event.

    Client chains

    Every CLI write records actor_client as zinn-cli/<package version>. Set ZINN_CLIENT to declare the programs calling the CLI. Zinn trims its surrounding whitespace and appends the remaining text after its own token, separated by one space. An empty or whitespace-only declaration leaves only the CLI token. The declared text is preserved without validating its format.

    			
    				
    						
    						ZINN_CLIENT='rex/0.9 zinn-opencode:panel/0.3.0' zinn whoami --json
    					
    			
    		

    For example, CLI version 0.44.0 records zinn-cli/0.44.0 rex/0.9 zinn-opencode:panel/0.3.0 for that declaration. With no declaration, it records zinn-cli/0.44.0.

    To format a declaration, see the client chain convention. Client declarations identify the calling programs and are not authenticated identities. The chain is also kept in the actor.client field of each local telemetry record.

    Agent skill

    zinn skill install installs the Zinn workflow skill bundled with this CLI. It runs npx --yes skills add <skill folder> -g -y with the skill folder inside the CLI’s package, and passes the skills CLI’s output and exit code through:

    			
    				
    						
    						zinn skill install
    					
    						
    						zinn skill install --json
    					
    			
    		

    With --json, stdout gets { "source": "<skill folder>" }, and the skills CLI’s output is reported as notices: on stderr after the result, or in error.diagnostics when the install fails. A failure’s error.details holds the skill folder as path and the skills CLI’s exitCode.

    Command aliases

    These aliases work in every namespace that has the primary command, with the same arguments, options and confirmations:

    Primary command Alias
    view show
    list ls
    delete rm
    create new
    edit update

    For example, zinn task show APP-1 runs zinn task view APP-1, and zinn task comment new APP-1 --text 'Verified' creates a comment. Alias words used as values stay values: zinn task list rm lists tasks in project rm.

    A command’s help names its full alias command. Root and namespace help use primary command names. Command discovery includes alias paths in each command descriptor.

    Help

    Use root help to discover commands and namespace help to find what each command does, including nested commands. Open a command’s help for its arguments and behavior:

    			
    				
    						
    						zinn --help
    					
    						
    						zinn project --help
    					
    						
    						zinn project column --help
    					
    						
    						zinn task --help
    					
    						
    						zinn task move --help
    					
    			
    		

    The short -h form works in the same positions. --help shows help for the command words before it and runs nothing. The rest of the line is ignored.

    Running zinn without a command shows help. Use zinn --version (or -v) to print the CLI version.