Skip to content

Eliminate input failures when updating Jira issues via MCP #467

Description

@williamthorsen

Eliminate INVALID_INPUT failures when updating Jira issues via MCP

Problem

Updates to Jira issues via the update_jira_issue MCP tool fail repeatedly with opaque INVALID_INPUT errors when the description_html or comment_html payload contains HTML that the agent has every reason to believe is valid (semantic HTML, well-formed, no obvious problems). Each failure provides zero diagnostic signal ({"errorMessages":["INVALID_INPUT"],"errors":{}}), so the agent burns iterations on blind retries.

The operation is high-frequency and the failure mode is recurring in well-defined ways. The agent is the unreliable step in the middle, regenerating HTML stochastically each time. The goal is to eliminate (or at least sharply reduce) these failures without over-engineering the first intervention.

Context

Observed failure triggers

Three concrete triggers have been seen repeatedly:

  1. Named HTML entities other than &/</> (e.g., —,  , …).
  2. Confluence-only macros (<ac:task-list>, <ac:structured-macro>, etc.) inadvertently included by an agent that has been working with Confluence content in the same session.
  3. File-path mode of the MCP tool — passing a path string in lieu of inline HTML.

Why these fail (inferred class)

The MCP tool advertises a permissive HTML surface, but the payload is converted into Atlassian Document Format (ADF) before persistence. The opaque error originates in that conversion. The general class of failure is:

HTML inputs that look valid to a generic HTML validator but cannot be losslessly mapped to Jira's ADF document model.

All three observed triggers fit this shape. There may be additional unknown instances of the class.

Why the agent is the right place to fix this (and why a sanitiser script is not the first move)

All three triggers are agent decisions about what to emit, not artefacts of an unavoidable transformation. The agent authors the HTML; it isn't sanitising third-party input. The first intervention should therefore be an explicit, prescriptive skill that names the rules — not a subprocess that polices the agent's output.

A deterministic sanitiser may be warranted later if explicit rules prove insufficient in practice. That decision should be gated on observed post-deploy failure rate, not on a hypothesis about reliability. To make that decision possible, the skill's recovery protocol must produce evidence (structured failure records) that can be reviewed periodically.

Existing constraints

  • The MCP tool's interface is fixed; we cannot extend it to accept Markdown directly.
  • The jira skill is a built-in platform skill (not part of this repo), so we cannot edit it.
  • Skills installed via codeassembly-agents install ship into ~/.rovodev/skills/<name>/ (or platform equivalents). The path-rewriter expands {platform_home_dir} literally inside SKILL.md.
  • codeassembly-agents is not installed onto user PATH; only the skill bundle ships globally.

Solution

Create a new user-invocable skill, update-jira-ticket, consisting of SKILL.md only — no helper script, no new dependencies, no install pipeline changes.

Mechanism

The skill body is short (~25–40 lines) and prescriptive. It covers:

  1. When the skill applies — any call to update_jira_issue with description_html or comment_html.
  2. Authoring rules — source content from a local Markdown artefact when one exists; otherwise compose in Markdown first. Convert to HTML using only the allowlisted elements; never hand-author HTML containing constructs outside the allowlist.
  3. Allowed elementsh1h6, p, ul, ol, li, strong, em, code, pre, a, blockquote, hr, br, table, thead, tbody, tr, th, td. Nothing else.
  4. Forbidden constructs<ac:*> macros (Confluence-only); named HTML entities other than &amp;/&lt;/&gt; (use literal Unicode: not &mdash;, not &hellip;, regular space or U+00A0 not &nbsp;).
  5. Invocation rules — always pass HTML inline; never use file-path mode of the MCP tool.
  6. Recovery protocol — if INVALID_INPUT still fires after following the rules: send <p>ok</p> to confirm call shape; bisect the payload to isolate the smallest failing fragment; cap at 4 retries; append a structured failure record to a known JSONL location (see below); surface the failing fragment to the user.
  7. Escalation criterion — explicitly named in the skill body: if recorded failures concentrate in known trigger classes, file a follow-up to add a deterministic sanitiser; if they distribute across unknown classes, the recovery protocol remains the right tool.

Failure record format (interim, until generic logging exists)

The skill instructs the agent, at the end of the recovery protocol, to append a single JSON object per line to ~/ai-artifacts/skill-failures/update-jira-ticket.jsonl. Each record contains at minimum: timestamp, skill (update-jira-ticket), project_slug, failing_fragment, and notes. A separate follow-up ticket will replace this ad-hoc location with a generic skill-failure logging mechanism.

Auto-load

The skill's description: frontmatter is phrased to trigger the agent's skill-loading heuristics whenever the agent is about to call update_jira_issue with description_html or comment_html. The skill is also user-invocable as !update-jira-ticket.

Acceptance criteria

  • A new skill update-jira-ticket exists at packages/agents/content/skills/update-jira-ticket/SKILL.md, with frontmatter that includes user-invocable: true and a description: phrased to auto-load on calls to update_jira_issue involving description_html or comment_html.
  • The skill body is body-only — no peer files, no helper script, no new package dependencies, no install-pipeline changes.
  • The skill body prescribes a single correct path: Markdown source → HTML constrained to the allowlist → inline payload, with explicit forbidden constructs (<ac:*>, named entities outside &amp;/&lt;/&gt;, file-path mode).
  • The skill body includes the recovery protocol (<p>ok</p> probe → bisection → 4-retry cap → surface failing fragment), and instructs the agent to append a structured JSONL failure record to ~/ai-artifacts/skill-failures/update-jira-ticket.jsonl after a failed recovery attempt.
  • The failure record format is documented inline in the skill body with at minimum these fields: timestamp (ISO 8601 UTC), skill (update-jira-ticket), project_slug, failing_fragment, notes.
  • The skill body names an explicit escalation criterion: under what observed failure pattern a follow-up to add a deterministic sanitiser would be warranted.
  • The skill installs successfully via codeassembly-agents install and the {platform_home_dir} mechanism (if used in the body) substitutes correctly.
  • Any documentation that enumerates skills (if such an index exists in the repo) includes update-jira-ticket. If no such index exists, this is a no-op.
  • A follow-up ticket is filed proposing a generic skill-failure logging mechanism (JSONL, with at least project_slug, skill, timestamp) that this skill (and others) can adopt in place of the interim ad-hoc location.

Metadata

Metadata

Labels

featureAdded or improved external functionalityscope:agents

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions