7aab0f04919c3e598426fb33d914051d57cbfa29 braney Sat Jul 25 09:53:29 2026 -0700 redmineCli: let update edit the ticket description refs #37281 Adds --description and --description-file to the update subcommand, which previously could not touch the description at all: that needed a raw API PUT. Mirrors how create handles the same two flags, with one difference. An empty --description clears the field rather than being an error, so the check is "is not None" instead of a truthiness test. The From Claude attribution is idempotent, so rewriting a description Claude already wrote does not stack a second header on it. diff --git src/utils/redmineCli src/utils/redmineCli index 8130aa030f7..fc83f38e107 100755 --- src/utils/redmineCli +++ src/utils/redmineCli @@ -3,30 +3,31 @@ Reads the Redmine API key from ~/.hg.conf (redmine.apiKey=...). Usage: redmineCli show 33571 redmineCli list --project maillists --status open --limit 10 redmineCli create --subject "Bug report" --description "Details here" redmineCli create --project genomebrowser --tracker 11 --subject "New track" \ --description "Details here" --assemblies hg38 # Track tracker requires Assemblies redmineCli build-patch --subject "v500 build patch: ..." --description "..." \ --developer braney --commit-id abc123 --files src/hg/js/hgTracks.js \ --cgis hgTracks --test-case "..." --reviewer hiram \ --target-version 500 --relates 27113 redmineCli comment 33571 --message "Adding a note" redmineCli update 33571 --status 1 --assigned-to lou --note "Reopening" + redmineCli update 33571 --description-file newDescription.txt redmineCli update 33571 --release-log-url "../cgi-bin/hgTrackUi?db=hg38&g=myTrack" redmineCli update 33571 --custom-field 46="some value" redmineCli attach 33571 screenshot.png --note "See attached" redmineCli note 20460 85 # show note-85 from ticket 20460 redmineCli relate 10316 15336 30368 # relate tickets to each other redmineCli watch 37339 lou braney # add watchers to a ticket redmineCli users # list user names and IDs """ import argparse import json import mimetypes import os import re import sys @@ -771,30 +772,40 @@ def cmd_update(args): """Update fields on an existing ticket.""" issue_data = {} if args.status is not None: issue_data["status_id"] = resolve_status(args.status) if args.assigned_to is not None: if args.assigned_to == "": issue_data["assigned_to_id"] = "" else: issue_data["assigned_to_id"] = resolve_user(args.assigned_to) if args.priority is not None: issue_data["priority_id"] = args.priority if args.subject is not None: issue_data["subject"] = strip_emoji(args.subject) + description = read_text_input(args.description, args.description_file) + if description is not None: + # An explicit empty string clears the description; anything else gets + # the same attribution treatment as create, which is idempotent so + # rewriting a description Claude already wrote will not double it up. + if description == "": + issue_data["description"] = "" + else: + issue_data["description"] = strip_emoji( + prepend_attribution(description)) if args.target_version is not None: if args.target_version == "": issue_data["fixed_version_id"] = "" else: issue = api_get(args.base_url, f"/issues/{args.ticket_id}.json", args.api_key) project_id = issue["issue"]["project"]["id"] issue_data["fixed_version_id"] = resolve_version( args.target_version, project_id, args.base_url, args.api_key) custom_fields = [] if args.category is not None: custom_fields.append({"id": CF_CATEGORY, "value": args.category}) if args.mlm is not None: custom_fields.append({"id": CF_MLM, "value": args.mlm}) @@ -839,30 +850,31 @@ custom_fields.append({"id": cf_id, "value": cf_val}) if custom_fields: issue_data["custom_fields"] = custom_fields note = read_text_input(args.note, args.note_file) if note: issue_data["notes"] = strip_emoji(prepend_attribution(note)) if args.private: issue_data["private_notes"] = True elif args.private: sys.exit("Error: --private requires --note or --note-file") if not issue_data: sys.exit("Error: no fields to update. Provide at least one of: " "--status, --assigned-to, --priority, --subject, " + "--description, --description-file, " "--target-version, --category, --mlm, " "--release-log-text, --release-log-url, " "--released-to-rr, --file-list, --file-list-add, " "--table-list, --assemblies, --custom-field, --note") data = {"issue": issue_data} api_put(args.base_url, f"/issues/{args.ticket_id}.json", args.api_key, data) print(f"Updated #{args.ticket_id}: {make_url(args.base_url, args.ticket_id)}") # --------------------------------------------------------------------------- # Subcommand: attach # --------------------------------------------------------------------------- def cmd_attach(args): @@ -1163,30 +1175,35 @@ p_comment.add_argument("--message", help="Comment text") p_comment.add_argument("--message-file", dest="message_file", help="Read comment from file (- for stdin)") p_comment.add_argument("--private", action="store_true", help="Mark this comment as a private note " "(only visible to project members with permission)") # update p_update = sub.add_parser("update", help="Update ticket fields") p_update.add_argument("ticket_id", help="Ticket ID number") p_update.add_argument("--status", help="New status name or ID (e.g. 'QA Ready' or 10)") p_update.add_argument("--assigned-to", dest="assigned_to", help="Assignee name/ID (empty string to clear)") p_update.add_argument("--priority", type=int, help="New priority ID") p_update.add_argument("--subject", help="New subject") + p_update.add_argument("--description", + help="Replace the ticket description (empty string " + "clears it)") + p_update.add_argument("--description-file", dest="description_file", + help="Read new description from file (- for stdin)") p_update.add_argument("--target-version", dest="target_version", help="Target version name or ID (empty string to clear)") p_update.add_argument("--category", help="MLQ Category") p_update.add_argument("--mlm", help="MLM name") p_update.add_argument("--release-log-text", dest="release_log_text", help="Release Log Text (custom field)") p_update.add_argument("--release-log-url", dest="release_log_url", help="Release Log URL (custom field)") p_update.add_argument("--released-to-rr", dest="released_to_rr", help="Released to RR (checkbox: 0/1/true/false/yes/no/on/off)") p_update.add_argument("--file-list", dest="file_list", help="File List (custom field, overwrites existing value)") p_update.add_argument("--file-list-add", dest="file_list_add", action="append", metavar="PATH", help="Append PATH to the File List custom field "