A saved Jira filter could always be ingested: "filter = 12345" is ordinary JQL,
and the fetch's own created >= watermark clause sits on top of it happily. What
was missing was the id. Nothing in the product named the filters an instance
holds, so the only way to reach one was to open Jira, find the filter, and read
the number out of the browser's address bar — which is enough friction that the
workflow may as well not exist, and the alternative is retyping a JQL query the
SOC already maintains in Jira.
list_filters returns each filter's id, name, JQL and owner, plus jql_reference:
the clause to paste, already written. Cloud reads filter/search, which pages
and can match a name server-side; Data Center reads filter/favourite, present
across versions where filter/search is not, and pages and matches locally. Both
response shapes — a paginated envelope, a bare array — were checked against
what the API actually returns.
get_incidents and query_issues now say a filter can go in their query box, and
which command hands over the id.
A personal access token silently outranked Basic credentials. Anything left in
pat — an email pasted into the wrong field, a token from a decommissioned Data
Center — became the Authorization header, and Atlassian answered "Client must
be authenticated to access this resource." That sentence names no field, so the
search starts at the URL, the API version, the token itself: everywhere except
the one field that caused it.
Basic authentication now wins whenever username and api_key are both set, and
pat serves the rest, so a token left behind cannot outrank credentials someone
has just filled in. A lone username, or a lone api_key, is not a pair and does
not claim the request. A 401 or 403 says which method was used and whether the
other was configured and ignored.
Three configurations that cannot work are refused with the reason rather than
sent to Jira:
- no usable credential, where an empty username/api_key pair was being
base64-encoded into a valid-looking header;
- the Cloud gateway URL with an empty cloud_id, which addresses no site, so
every path under it is a dead link and Jira replies with an HTML 404 that
explains nothing;
- a cloud_id set against any other host, where it is not an address at all,
only a path segment that does not exist.
Credentials are stripped before use: one pasted with a trailing newline
corrupts the header and reads back as a wrong password.
Error bodies are condensed instead of dumped. Jira's 404 page is four hundred
lines of AUI markup, stylesheet links and inline scripts around one sentence,
and it was going into the test modal whole; what surfaces now is the <title>
and the readable text, capped. A 2xx that is not JSON — a login page, an SSO
redirect, a proxy standing in for the API — reports what answered and the URL
the redirects landed on, in place of a JSON parser offset.
Config descriptions now say which fields pair with which deployment, and which
credential is used when both are present.
Applied to all 27 scripts; the seven shared helper blocks stay identical across
them.
Jira Cloud and On-Prem/Data Center (27 commands): JQL ingestion with an
OCSF mapper and JQL query, full issue lifecycle (create/get/edit/delete,
transitions, assign), comments, remote web links and issue links,
attachments (upload/download/delete), field and user lookups, and agile
boards/sprints/epics. Basic (email + API token) or Personal Access Token
authentication; API v3 + ADF bodies on Cloud, v2 on On-Prem.
Stdlib-only, no extra Python dependencies.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>