<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kadir Emre Parlak</title>
    <description>The latest articles on DEV Community by Kadir Emre Parlak (@keparlak).</description>
    <link>https://dev.to/keparlak</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1567492%2F50dafc31-1ae3-4176-9cc2-4ba828967c32.jpg</url>
      <title>DEV Community: Kadir Emre Parlak</title>
      <link>https://dev.to/keparlak</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/keparlak"/>
    <language>en</language>
    <item>
      <title>You can't set a status in Jira</title>
      <dc:creator>Kadir Emre Parlak</dc:creator>
      <pubDate>Mon, 10 Aug 2026 20:01:12 +0000</pubDate>
      <link>https://dev.to/keparlak/you-cant-set-a-status-in-jira-4d7p</link>
      <guid>https://dev.to/keparlak/you-cant-set-a-status-in-jira-4d7p</guid>
      <description>&lt;p&gt;Agents write to work trackers now, and the trackers have started modelling them as first-class citizens. Linear's GraphQL schema has &lt;code&gt;AgentSession implements Node&lt;/code&gt; and &lt;code&gt;AgentActivity implements Node&lt;/code&gt; — not a webhook bolted on the side, but entities alongside &lt;code&gt;Issue&lt;/code&gt; and &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Writing is where this gets interesting, because a tracker is not a database with a &lt;code&gt;status&lt;/code&gt; column. It is a state machine with opinions, and every vendor's opinions are different.&lt;/p&gt;

&lt;p&gt;Here are three that broke my assumptions, in ascending order of how wrong I was.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub: there is no hierarchy
&lt;/h2&gt;

&lt;p&gt;The easy one. GitHub issues are flat and binary — open or closed. There is no epic, no parent, no workflow. If your process has "this story belongs to that epic", GitHub has nothing to map it onto.&lt;/p&gt;

&lt;p&gt;You can emulate it with labels. That works. The important part is what happens when you do: you have now invented a convention that exists nowhere in the provider, and anything reading those issues without knowing your convention sees a flat list with some odd labels on it.&lt;/p&gt;

&lt;p&gt;That is a fine trade. It is not fine to make it &lt;em&gt;silently&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Linear: there is no "in review"
&lt;/h2&gt;

&lt;p&gt;Linear has a &lt;code&gt;WorkflowState&lt;/code&gt; with a &lt;code&gt;type&lt;/code&gt; field, and you would reasonably expect that type to be the vocabulary. Here is the schema's own description of the entity:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A state in a team's workflow, representing an issue status such as Triage, Backlog, Todo, &lt;strong&gt;In Review&lt;/strong&gt;, Done, or Canceled. […] Workflow states have a type that categorizes them (&lt;code&gt;triage&lt;/code&gt;, &lt;code&gt;backlog&lt;/code&gt;, &lt;code&gt;unstarted&lt;/code&gt;, &lt;code&gt;started&lt;/code&gt;, &lt;code&gt;completed&lt;/code&gt;, &lt;code&gt;canceled&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read those two lists against each other. "In Review" appears in the first — it is a state a team really has — and nowhere in the second. Review is not a &lt;em&gt;type&lt;/em&gt;; it is a state that happens to be typed &lt;code&gt;started&lt;/code&gt;, exactly like "In Progress". So you cannot map an abstract "in review" onto a state type. You have to map it onto a specific state, by ID, per team.&lt;/p&gt;

&lt;p&gt;(The schema disagrees with itself about the type list, incidentally. The entity description includes &lt;code&gt;triage&lt;/code&gt;; the description on the &lt;code&gt;type&lt;/code&gt; field itself lists only &lt;code&gt;backlog, unstarted, started, completed, canceled&lt;/code&gt;. Whichever is right, it is not a vocabulary you want to hardcode.)&lt;/p&gt;

&lt;p&gt;Two further details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WorkflowState.type&lt;/code&gt; is &lt;code&gt;String!&lt;/code&gt;, not an enum. It is unversioned and the provider can extend it. Anything you switch on today is a guess about tomorrow.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WorkflowState.team&lt;/code&gt; is &lt;code&gt;Team!&lt;/code&gt; — non-null. Workflows belong to &lt;em&gt;teams&lt;/em&gt;, so two teams in one workspace legitimately have different states for the same conceptual step, and a single flat mapping cannot represent that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And &lt;code&gt;triage&lt;/code&gt; has no equivalent in most abstract models at all. What is your agent supposed to do with an issue in triage?&lt;/p&gt;

&lt;h2&gt;
  
  
  Jira: you cannot set the status
&lt;/h2&gt;

&lt;p&gt;This is the one that broke my design.&lt;/p&gt;

&lt;p&gt;I assumed a work item had a status, and that writing to it meant setting a field. In Jira it does not. You must first call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /rest/api/3/issue/{issueIdOrKey}/transitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which returns the transitions &lt;em&gt;currently permitted&lt;/em&gt; — which depend on the project's workflow, the issue type, and the item's present state. Then you post the transition id. And the transition may have a screen attached, in which case it demands required fields before it will go through.&lt;/p&gt;

&lt;p&gt;So "move this to in progress" is not a write. It is: discover what is possible, match your intent against it, supply whatever the transition screen demands, then act. An agent that assumes otherwise does not fail cleanly — it fails after it has already done half the work.&lt;/p&gt;

&lt;p&gt;There is a second trap. Jira's &lt;code&gt;statusCategory&lt;/code&gt; looks like the canonical vocabulary, and it has exactly three values: &lt;code&gt;TODO&lt;/code&gt;, &lt;code&gt;IN_PROGRESS&lt;/code&gt;, &lt;code&gt;DONE&lt;/code&gt;. If your model has "ready" or "in review" or "blocked", none of them are derivable from it. You need per-project status mapping, or you need to give up on those concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing everybody does instead
&lt;/h2&gt;

&lt;p&gt;The common workaround is to put the vendor's own state name in the prompt. Here is OpenAI's Symphony — an orchestrator that runs coding agents against a tracker — in its reference workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;update_issue(..., state: "In Progress")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its specification is explicit that this is by design. §11.5, "Tracker Writes and Agent Tools":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Symphony does not require first-class tracker write APIs in the orchestrator. Ticket mutations (state transitions, comments, attachments, PR metadata) are typically handled by the coding agent through the selected adapter's provider-native tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both of its required adapter functions are reads. Writing is left to the agent.&lt;/p&gt;

&lt;p&gt;That is a reasonable boundary for an orchestrator to draw. But it pushes the problem into the prompt, and a prompt containing &lt;code&gt;"In Progress"&lt;/code&gt; is a prompt that breaks when someone renames a column, when a second project uses a different workflow, or when you migrate trackers. It is also precisely the string an agent will hallucinate a plausible variant of.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I think the shape of the answer is
&lt;/h2&gt;

&lt;p&gt;Three parts, and the second is the one people skip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speak in roles, not states.&lt;/strong&gt; A small abstract vocabulary — &lt;code&gt;backlog → ready → in_progress → in_review → done&lt;/code&gt;, plus &lt;code&gt;blocked&lt;/code&gt; — that prompts and workflows are written against. Nothing new here; every integration layer has some version of this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolve the mapping once, with a human, and commit it.&lt;/strong&gt; This is the part that matters. Every runtime approach I have seen has the model discover valid transitions and pick one on each call, which means the answer can differ between two runs for reasons nobody can see. Instead: introspect the provider's actual states at setup, propose a mapping, have a human confirm it, and commit the result to the repo as configuration. Now the mapping is reviewable, diffable, and identical on every run. A wrong mapping becomes a pull request comment instead of a mystery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Declare the gaps and negotiate them out loud.&lt;/strong&gt; For every capability a provider lacks, pick a behaviour explicitly: fail loudly, emulate it (GitHub hierarchy via labels), or degrade with a warning. The rule I hold myself to is that a gap which is neither errored nor logged is a bug. This is what stops the abstraction from lying — you are told when you have left the paved road, rather than discovering it from a support ticket three weeks later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I got it wrong
&lt;/h2&gt;

&lt;p&gt;I built this and then found my own model had the same disease.&lt;/p&gt;

&lt;p&gt;I had six workflow roles, and &lt;code&gt;blocked&lt;/code&gt; was one of them. The documentation right above the code said &lt;code&gt;blocked (orthogonal)&lt;/code&gt; — I knew, when I wrote it, that being blocked is not a &lt;em&gt;stage&lt;/em&gt; of work but a condition on top of one. The code did not agree with the comment. Transitioning an item to &lt;code&gt;blocked&lt;/code&gt; destroyed whatever role it had, so "blocked while in review" was unrepresentable.&lt;/p&gt;

&lt;p&gt;Linear says this out loud in the same schema I had just been reading: &lt;code&gt;IssueRelationType&lt;/code&gt; is &lt;code&gt;blocks&lt;/code&gt;, &lt;code&gt;duplicate&lt;/code&gt;, &lt;code&gt;related&lt;/code&gt;. Blocking is a relation, sitting right next to the workflow states rather than inside them. Jira and GitLab agree. All three would have broken my design, and the fix — making it a relation instead of a state — is nearly free with zero users and expensive with a hundred.&lt;/p&gt;

&lt;p&gt;The general lesson, if there is one: an abstraction designed against two providers is not an abstraction, it is an average. I designed mine against Azure DevOps and GitHub, which are genuinely different from each other, and it still did not survive contact with a third.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This came out of building &lt;a href="https://github.com/loncadev/baron" rel="noopener noreferrer"&gt;Baron&lt;/a&gt;, an open-source layer that does the above — normalized writes to work trackers from a coding agent, with the role mapping committed to your repo. It is early and I would rather have bug reports than stars.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
