From 779454ed72351cfb96d22733ea21cf0dae8891af Mon Sep 17 00:00:00 2001 From: William Thorsen Date: Mon, 27 Apr 2026 21:11:06 -0700 Subject: [PATCH] agents|feat: Add update-jira-ticket skill to prevent INVALID_INPUT failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new user-invocable skill, `update-jira-ticket`, that establishes one prescriptive path for updating a Jira issue's description or comment via the `update_jira_issue` MCP tool. The skill prevents the recurring opaque `INVALID_INPUT` failure class by constraining HTML to a narrow allowlist (`h1`–`h6`, `p`, lists, `a`, `code`/`pre`, tables, etc.) and explicitly forbidding the three observed triggers: named HTML entities other than `&`/`<`/`>`, `` Confluence macros, and the MCP tool's file-path mode. The skill body is prescriptive rather than exploratory: agents source content as Markdown (preferring local artefacts), convert to HTML using only the allowlist, and pass it inline. A bounded recovery protocol (probe with `

ok

`, bisect, cap at 4 retries) is included as a backstop for unknown failure classes. The recovery protocol also instructs the agent to append a structured JSONL failure record (timestamp, skill, project_slug, failing_fragment, notes) to `~/ai-artifacts/skill-failures/update-jira-ticket.jsonl` after a failed recovery, so a future review can decide on data — not vibes — whether to escalate to a deterministic sanitiser script. A separate follow-up (#468) covers replacing this interim ad-hoc location with a generic skill-failure logging mechanism. The skill is body-only — no peer files, no helper scripts, no new dependencies, no install-pipeline changes. It ships through the existing skill-bundle install path. --- .../skills/update-jira-ticket/SKILL.md | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 packages/agents/content/skills/update-jira-ticket/SKILL.md diff --git a/packages/agents/content/skills/update-jira-ticket/SKILL.md b/packages/agents/content/skills/update-jira-ticket/SKILL.md new file mode 100644 index 00000000..f5fe0562 --- /dev/null +++ b/packages/agents/content/skills/update-jira-ticket/SKILL.md @@ -0,0 +1,80 @@ +--- +name: update-jira-ticket +description: 'Use whenever updating a Jira issue description or comment via the update_jira_issue MCP tool. Prevents the recurring INVALID_INPUT failure class by constraining HTML to a narrow allowlist and forbidding named entities, Confluence macros, and file-path mode.' +user-invocable: true +--- + +# Update Jira ticket + +Use whenever calling `update_jira_issue` (or `create_jira_issue`) with `description_html` or `comment_html`. The MCP tool advertises a permissive HTML surface, but the payload is converted to Atlassian Document Format (ADF) before persistence and frequently rejects valid-looking HTML with an opaque `INVALID_INPUT` error. This skill prescribes the one path that avoids the known triggers. + +## The one correct path + +1. **Source content as Markdown.** Prefer a local Markdown artefact when one exists. Otherwise, compose in Markdown first — never author HTML directly. +2. **Convert Markdown to HTML using only the allowlist below.** Anything outside the allowlist must be omitted or rewritten. +3. **Pass the HTML inline** to `description_html` or `comment_html`. +4. **Never pass a file path** to `description_html` / `comment_html`. File-path mode is forbidden — it has been observed to fail with `INVALID_INPUT`. + +## Allowed elements + +Exhaustive list. Nothing else. + +`h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `p`, `ul`, `ol`, `li`, `strong`, `em`, `code`, `pre`, `a`, `blockquote`, `hr`, `br`, `table`, `thead`, `tbody`, `tr`, `th`, `td` + +**Always strip `` constructs unconditionally.** These are Confluence storage-format extensions (e.g., ``, ``) and have no Jira analogue. Including them produces `INVALID_INPUT`. If you have been working with Confluence content in the same session, audit the payload before sending. + +## Character handling + +Use **literal Unicode** in HTML. Do not use named HTML entities outside the three universally-safe ones. + +| Don't write | Write instead | +| ----------- | -------------------------- | +| `—` | `—` (U+2014) | +| `–` | `–` (U+2013) | +| `…` | `…` (U+2026) | +| ` ` | regular space, or `\u00a0` | +| `©` | `©` (U+00A9) | +| `’` | `'` (U+2019) | + +Only `&`, `<`, `>` are valid in payload text. `"` and `'` are valid only inside attribute values where they're needed to avoid clashing with the attribute's quote style. + +## Recovery protocol (backstop) + +Use only if `INVALID_INPUT` still fires after following the rules above. + +1. **Probe.** Send `

ok

` as the entire payload. If this also fails, the problem is call shape, permissions, or the issue itself — not the payload. Stop and report. +2. **Bisect.** If `

ok

` succeeds, the failure is in the payload's content. Bisect the payload (split in half, test each half, recurse) to isolate the smallest fragment that still triggers `INVALID_INPUT`. +3. **Cap retries.** Do not exceed 4 retry attempts beyond the original failure. If the bisection has not converged by then, surface the smallest failing fragment to the user and stop. +4. **Record the failure.** Append a single JSON object (one line, no trailing comma) to `~/ai-artifacts/skill-failures/update-jira-ticket.jsonl`. Create the directory and file if absent. + + Required fields: + + ```json + { + "timestamp": "2026-04-28T03:15:32Z", + "skill": "update-jira-ticket", + "project_slug": "codeassembly", + "failing_fragment": "...", + "notes": "matched known trigger: " + } + ``` + + - `timestamp`: ISO 8601 UTC. + - `skill`: literal string `update-jira-ticket`. + - `project_slug`: basename of the repo root (or whatever convention the agent already uses for artefact paths in this session). + - `failing_fragment`: the smallest payload fragment that reproduced `INVALID_INPUT`. + - `notes`: free-form. Name the suspected trigger class if recognisable, otherwise leave empty. + +## Escalation criterion + +If recorded failures concentrate in **known trigger classes** (named entities, ``, file-path mode) at frequency that costs real iterations, file a follow-up to add a deterministic sanitiser script to this skill — see [#467](https://github.com/williamthorsen/codeassembly/issues/467) for the prior decision and [#468](https://github.com/williamthorsen/codeassembly/issues/468) for the generic-logging follow-up. + +If failures distribute across **unknown classes** (no clear pattern), the recovery protocol remains the right tool. A sanitiser would not help, since it can only enforce known rules. + +## Antipatterns + +- Hand-authoring HTML containing constructs outside the allowlist. +- Using named HTML entities other than `&`, `<`, `>`. +- Passing a file path to `description_html` / `comment_html`. +- Retrying past the 4-retry cap. +- Skipping the failure record after a recovery — this removes the evidence needed to decide whether to escalate.