- Published on
Writing Great Tickets: The Craft of Issues for Humans and AI Agents
- Authors

- Name
- Youngju Kim
- @fjvbn20031
"Code is written once and read a hundred times. A ticket is written once and lives as long as the code it produced."
Prologue — The Ticket Is the Spec
Every software team has one artifact it writes most often and teaches least: the ticket. Jira issue, GitHub Issue, Linear task — the names differ, the essence is the same. It is a piece of writing that says what someone should build, and why.
Many people treat tickets casually. "I'll just explain it in chat." "They'll figure it out from the code." But the reality is harsher. A ticket is the spec. It is the only contract a developer holds in their head when they open a PR. It is the standard a reviewer uses to decide "is this right?" It is the last clue someone hits six months later, riding git blame into the dark.
A bad ticket produces a bad outcome. A vague ticket produces a vague PR. A ticket with hidden scope produces an unreviewable diff. A ticket with no acceptance criteria produces a meeting where "it's done" and "no it isn't" collide.
And in 2026, this problem doubled in size. Because tickets are no longer read only by humans.
AI agents read tickets too
When you hand a task to a coding agent, the input is, in the end, a ticket. A human teammate handed a vague ticket will ask in Slack, guess at context, or walk over to someone's desk. An agent does none of that. An agent works with only what the ticket says. Whatever is not written, it hallucinates, ignores, or fills with the most plausible default.
In other words, ticket quality is the ceiling on outcome quality. Humans had a safety net — "you can always ask." Agents do not. The skill of writing great tickets has crossed from a collaboration skill into a core skill for working with AI.
The good news: a ticket that is good for humans is good for agents. Explicit context, verifiable acceptance criteria, narrow scope — these three work identically for people and for AI. This article is a practical guide to getting those three onto the page.
What this article covers:
| Chapter | Topic |
|---|---|
| 1 | The anatomy of a great ticket — five components |
| 2 | Acceptance criteria as checkboxes |
| 3 | Scoping — one ticket = one PR |
| 4 | The bug ticket template |
| 5 | The feature ticket template |
| 6 | AI-ready tickets vs human-only tickets |
| 7 | Linking, traceability, the issue graph |
| 8 | Ticket-writing anti-patterns |
| 9 | Copy-paste templates |
| Epilogue | Checklist + anti-patterns + next-post teaser |
Chapter 1 · The Anatomy of a Great Ticket
A great ticket has five components. Memorize them and they work in any tracker.
1.1 The five components
| Component | The question it answers | What happens without it |
|---|---|---|
| Context | Why are we doing this now? | You solve the wrong problem |
| Problem | What is broken or missing? | You patch the symptom, miss the cause |
| Acceptance criteria | What must be true to call it "done"? | "It's done" disputes erupt |
| Constraints | What must not be touched? | Out-of-scope changes creep in |
| References | Where is the related code, doc, design? | The worker spends 30 minutes just searching |
A ticket with these five filled in is self-sufficient. The worker can start without digging through Slack, without asking the next desk over. And a self-sufficient ticket is exactly what an AI agent needs.
1.2 Context: lead with "why"
Context is the most frequently omitted component and the most expensive one to lose. "Change the button color to blue" is an instruction, not context. What the worker needs is the why.
A brand refresh moved the primary action color from purple to blue (see Design System v3). This button is the key CTA in the checkout flow, so it must follow the new color.
With that one sentence, the worker can judge whether other similar buttons need attention too, and the agent can infer whether this is a token-based change to "primary action color" or a one-off hardcode.
1.3 Problem: facts, not symptoms
A problem statement should be an observable fact. Do not mix in interpretation or guesswork.
- Bad: "Login is kind of weird sometimes"
- Good: "Users whose password contains a
+character get a 401 on login. Suspected missing URL encoding, but unconfirmed."
The good version separates the reproduction condition (password with +), the observed result (401), and guess from fact ("suspected", "unconfirmed").
Chapter 2 · Acceptance Criteria as Checkboxes
Acceptance criteria (AC) are the single most important part of a ticket. What AC does is simple: it nails down the definition of "done" before work begins.
2.1 Why checkboxes
AC should be written as a checkbox list, not prose. The reasons are concrete.
## Acceptance Criteria
- [ ] Logging in with a password containing `+` returns 200
- [ ] Passwords containing `&`, `=`, or a space behave identically
- [ ] An incorrect password still returns 401 (regression guard)
- [ ] An integration test covering this case is added
Why checkboxes work:
| Perspective | What checkboxes provide |
|---|---|
| Worker | A to-do list. Cross them off and you're done |
| Reviewer | A review checklist. Match the PR against each item 1:1 |
| PM / reporting | Progress is visible (3/4 complete) |
| AI agent | Verifiable goals. Each line translates into a test case |
The last row is the key. An agent cannot infer "this is probably enough" from prose. But - [ ] X returns Y becomes a verification loop directly: write the code, check if that line is true, and if not, write it again.
2.2 What makes a good AC
A good AC line satisfies the following:
- Verifiable — true/false can be judged objectively
- States observable behavior — the result, not the implementation
- Holds exactly one fact — does not bundle two with "and"
A comparison:
| Bad AC | Good AC |
|---|---|
| Login works well | Logging in with valid credentials redirects to /dashboard |
| Improve performance | The product list API's p95 response time is under 200ms |
| Add error handling | On network failure, a toast "Please try again" is shown |
| Clean up the code | (not an AC — remove it from acceptance criteria) |
Look at the last row. "Clean up the code" is unverifiable. It is not an AC; pull it out into a work note or a separate refactoring ticket.
2.3 The Given-When-Then variant
For complex behavior, the Given-When-Then format reduces ambiguity.
## Acceptance Criteria
- [ ] **Given** the cart contains an out-of-stock item
**When** the checkout button is pressed
**Then** a modal "Some items are out of stock" appears and checkout does not proceed
- [ ] **Given** all items have sufficient stock
**When** the checkout button is pressed
**Then** the user moves to the checkout confirmation page
This format clarifies the scenario for humans and hands the agent an almost ready-made test skeleton.
Chapter 3 · Scoping — One Ticket = One PR
The most powerful and most frequently ignored rule in ticket writing.
One ticket should resolve into one reviewable PR.
3.1 What "reviewable" means
A reviewable PR is a diff one person can read end to end in one sitting and approve with confidence. As a rule of thumb, around 400 changed lines, around 10 core files. Cross that line and the reviewer gives up reading and clicks "LGTM" — at which point the review is meaningless.
When a ticket is sized to fit this, good things cascade.
| When the ticket is small | Result |
|---|---|
| The PR is small | The review is fast and thorough |
| The change is isolated | If something breaks, it is easy to revert |
| The scope is clear | No "should I do this too?" agonizing |
| Progress is visible | Big work splits into small completions |
| An agent can handle it | It all fits inside the context window |
The last row is the new reason in the AI era. A giant ticket overflows the agent's context. Halfway through the work it "forgets" the beginning and loses consistency. A small ticket fits in one context from start to finish.
3.2 How to split a large ticket
"Implement user authentication" is not a ticket — it is an epic. It must be split.
Epic: User Authentication
├─ #101 Email/password signup (DB schema + endpoint)
├─ #102 Login + session token issuance
├─ #103 Password reset flow (including email send)
├─ #104 Login UI component
└─ #105 Apply auth middleware to protected routes
Criteria for splitting:
- Vertical slices first — "signup feature end to end" beats a horizontal split like "the whole backend"
- Is each slice independently shippable — if
#102is meaningless without#101, state the dependency - Does each slice have its own AC — if not, it is still too big
3.3 The "hidden scope" trap
The most dangerous case: the ticket title looks small but the body quietly grows.
Title: Add a dark mode toggle to the header Body: ...add the toggle, and while you're at it, also tidy up the design token system and migrate all color variables to CSS variables.
A single toggle has morphed into a company-wide color migration. A ticket like this must be split in two. The token migration becomes a separate ticket; the toggle sits on top of it as a dependency.
Chapter 4 · The Bug Ticket Template
Bug tickets have a fixed shape. Leave even one field empty and the worker starts guessing — and guessing burns time.
4.1 The required fields of a bug ticket
| Field | Content |
|---|---|
| Repro steps | Numbered 1, 2, 3 — following them must reproduce it identically |
| Expected result | What should have happened |
| Actual result | What did happen |
| Environment | OS, browser/runtime version, app version, deploy environment |
| Evidence | Stack trace, logs, screenshots, network response |
| Frequency | Always / sometimes / once — the debugging strategy diverges |
4.2 Before / After
Before — a bug ticket that forces guessing:
Title: Payment doesn't work There's an error on the payment page. Please check.
What the worker doesn't know: which payment method? what amount? what error? what environment? always? what are the repro steps? This ticket effectively says "you figure it out."
After — a bug ticket you can debug immediately:
## Summary
A 500 error occurs for credit card payments above 50,000 KRW
## Repro Steps
1. Add 60,000 KRW worth of items to the cart
2. Go to the payment page, select credit card
3. Enter card details and click "Pay"
## Expected Result
After payment approval, move to the order complete page
## Actual Result
A toast "A temporary error occurred", payment not completed
## Environment
- Deploy environment: production
- Browser: reproduces on both Chrome 124 and Safari 17
- Started: suspected since the May 12 14:00 deploy
## Evidence
- Server log: `PaymentError: amount exceeds limit (50000)`
- Request ID: req_8f2a91c
- Screenshot: attached
## Frequency
Always reproduces above 50,000 KRW (100%)
The After version lets the worker form a root-cause hypothesis in 30 seconds — the amount-limit validation that came in with the May 12 deploy is the suspect. An agent would follow the log's error message and request ID straight to the relevant code path.
4.3 Stack traces go in code blocks
When pasting a stack trace or logs, always put them inside a code block. Pasting them raw into prose is hard to read and breaks as markdown in some trackers.
## Evidence
```
TypeError: Cannot read properties of undefined (reading 'id')
at resolveUser (src/auth/resolver.ts:42:18)
at processRequest (src/server/handler.ts:118:9)
```
A trace with file paths and line numbers (src/auth/resolver.ts:42) is the best entry point for both humans and agents.
Chapter 5 · The Feature Ticket Template
A feature ticket is structured differently from a bug ticket. A bug deals with "what broke"; a feature deals with "what to build new." The core is acceptance criteria and scope boundaries.
5.1 The required fields of a feature ticket
| Field | Content |
|---|---|
| Background / problem | What user or business problem does this solve |
| Proposed change | What to build (behavior, not implementation detail) |
| Acceptance criteria | A checkbox list — the definition of "done" |
| Out of scope | What you explicitly will not do this time |
| Constraints | Performance budget, compatibility, what not to touch |
| References | Design, related code, predecessor tickets |
Emphasize the Out of scope field in particular. Writing down what you will not do is as powerful as writing down what you will. This field is what stops hidden scope from multiplying.
5.2 Before / After
Before — a vague feature ticket:
Title: Improve search I want search to be smarter. Users can't find what they want.
"Smarter" and "well" are unverifiable. The worker cannot tell whether to build autocomplete, add typo correction, or change ranking.
After — a workable feature ticket:
## Background
In product search, if a user doesn't know the exact product name, results come back empty.
Per last week's search logs, 38% ended in zero results.
## Proposed Change
Apply product-name partial matching plus typo correction (edit distance 1) to the query.
## Acceptance Criteria
- [ ] Searching "macbook" includes "MacBook Pro 16" in the results
- [ ] Searching "makbook", a typo of "macbook", returns the same results
- [ ] When there are no results, 5 "you might like these" suggestions are shown
- [ ] The search API p95 response time stays under 300ms
## Out of Scope
- Voice search
- Changes to the search result ranking algorithm (separate ticket #210)
- Category filter UI
## Constraints
- Do not change the response schema of the existing `/api/search` endpoint
- Search index re-indexing must be possible with zero downtime
## References
- Design: Figma link
- Zero-result analysis: Notion doc link
- Related code: `src/search/query-builder.ts`
The After version gives the worker a clear finish line, and for the agent, each AC becomes a verification step directly. The Out of scope field turns a 30-minute "should I do ranking too?" deliberation into zero seconds.
Chapter 6 · AI-Ready Tickets vs Human-Only Tickets
Not every ticket is good to hand to an agent. Some tickets should be handled only by humans; some need only a light touch before an agent can finish them. Make this distinction explicit with labels and the whole team shares one standard.
6.1 What makes a ticket AI-ready
| Traits of an AI-ready ticket | Signals to keep it human-only |
|---|---|
| Acceptance criteria are verifiable | Subjective goals like "it should just feel better" |
| Scope is narrow and clear | Vague exploratory work spanning multiple systems |
| Reference code paths are specified | Where to touch is itself the investigation |
| Work that follows existing patterns | Work that needs a new architecture decision |
| Result can be confirmed by tests | Heavy dependence on production data / external systems |
| Easy-to-revert change | High-risk areas like migrations, security, payments |
The core insight: an AI-ready ticket is really just a well-written ticket. It is not a separate species. Write it clearly for humans and an agent can handle it too. Conversely, a ticket an agent gets lost in usually loses a human new hire too.
6.2 Label strategy
Signal intent with tracker labels. An example scheme:
| Label | Meaning |
|---|---|
agent-ready | AC is verifiable and scope is narrow — can be delegated to an agent |
agent-assisted | An agent drafts it, a human finishes and judges |
human-only | Architecture decisions, high-risk areas, vague exploration |
needs-refinement | No AC yet or scope too big — needs grooming |
needs-refinement matters. This label is an honest signal that "this ticket cannot yet go to anyone." In grooming, you refine the tickets carrying this label and graduate them to agent-ready or human-only.
6.3 The check before handing it to an agent
What to confirm before putting agent-ready on a ticket:
- [ ] Are all acceptance criteria checkboxes and verifiable
- [ ] Are the relevant code paths/files written in references
- [ ] Is the boundary drawn with an "out of scope" field
- [ ] Did you specify which pattern in the existing code to follow
- [ ] Is there a test method to confirm the result
- [ ] Is this not a high-risk area (auth, payments, migration)
If it does not pass these six lines, it is still needs-refinement.
Chapter 7 · Linking, Traceability, the Issue Graph
A ticket is not an island. A good team's tracker is a graph — tickets connected to each other and to the code.
7.1 The five links you should connect
| Link direction | Example | Why |
|---|---|---|
| Ticket to parent epic | #102 belongs to the "User Authentication" epic | Its place in the big picture |
| Ticket to dependency ticket | #102 depends on #101 being complete | Work order becomes visible |
| Ticket to PR | A PR link in the body of #102 | What resolved this ticket |
| PR to ticket | Closes #102 in the PR description | Auto-close on merge |
| Ticket to doc / design | RFC, Figma, postmortem | The basis of the decision |
A keyword like Closes #102 auto-closes the issue on PR merge in GitHub/GitLab. This one small habit eliminates the zombie ticket that "someone forgot to close."
7.2 The value of traceability
Suppose six months later someone finds strange code. If traceability is alive, the path is this.
One line of strange code
-> git blame -> the commit
-> the commit -> the PR
-> the PR -> ticket #102
-> the ticket -> epic + RFC link
-> "ah, that's why it was written this way"
If this chain is broken, that code stays a mystery forever and nobody dares touch it. The ticket-PR-commit link is a letter to your future self.
7.3 AI agents and the issue graph
When you give #102 to an agent, if a dependency link exists the agent can pull #101's output (schema, endpoint) in as context. Without the link, the agent sees #102 as an isolated island, rebuilds what already exists, or breaks consistency. The issue graph is the agent's context map.
Chapter 8 · Ticket-Writing Anti-Patterns
Knowing the opposite of a great ticket makes you better faster. The anti-patterns most often seen in the field.
8.1 Vague verbs
"Improve", "optimize", "clean up", "make better" — these verbs are unverifiable. Always swap them for a measurable result.
| Vague verb | Measurable version |
|---|---|
| Improve search | Lower the zero-result rate from 38% to under 10% |
| Optimize the page | Lower LCP from 4.1s to under 2.5s |
| Clean up error handling | Show a user-facing error message on every API failure |
8.2 Hidden scope
Covered in Chapter 3. The pattern where the title is small but the body quietly grows. If "while we're at it" or "as a bonus" appears in the body, it is almost always a separate-ticket signal.
8.3 Missing acceptance criteria
A ticket that starts with no definition of "done." At the end, a "is this actually done?" meeting opens. A ticket with no AC should not be started.
8.4 Multiple tasks in one ticket
A ticket bundled like "fix the login bug, also polish the signup UI, also add logging." It is impossible to review, and if one part stalls, all of it stalls.
8.5 Prescribing the solution and hiding the problem
"Add an index to the users table" — why? which query is slow? Without the problem, the worker cannot verify it is the right solution, nor propose a better one. Write the problem; keep the solution at the level of a suggestion.
8.6 Anti-pattern summary table
| Anti-pattern | Symptom | Prescription |
|---|---|---|
| Vague verb | "improve / optimize / clean up" | Turn into a measurable result |
| Hidden scope | Body bigger than the title | Split the ticket |
| Missing AC | No definition of "done" | Add checkbox AC |
| Multiple tasks | Several PRs' worth in one ticket | Split into one each |
| Prescribing the solution | No "why" | Add a problem statement |
| Absent context | No "why now" | Add a background paragraph |
Chapter 9 · Copy-Paste Templates
Drop the templates below straight into your repository's .github/ISSUE_TEMPLATE/ or your tracker templates.
9.1 Bug report template
## Summary
{In one sentence, what broke}
## Repro Steps
1.
2.
3.
## Expected Result
{What should have happened}
## Actual Result
{What did happen}
## Environment
- Deploy environment: {production / staging / local}
- Browser/runtime: {version}
- App version/commit: {version or SHA}
## Evidence
```
{Stack trace / logs / request ID}
```
{Attach screenshot}
## Frequency
{Always / sometimes (M out of N) / once}
9.2 Feature request template
## Background
{What user/business problem does this solve, with data if possible}
## Proposed Change
{What to build — at the level of behavior, not implementation}
## Acceptance Criteria
- [ ] {Verifiable result 1}
- [ ] {Verifiable result 2}
- [ ] {Regression guard item}
- [ ] {Test-added item}
## Out of Scope
- {What you will not do this time, 1}
- {What you will not do this time, 2}
## Constraints
- {Performance budget / compatibility / what not to touch}
## References
- Design:
- Related code:
- Predecessor ticket:
9.3 Task template (for refactoring / chores)
## What
{One-sentence task description}
## Why
{Why this is needed now}
## Acceptance Criteria
- [ ] {Result 1}
- [ ] {Item confirming existing behavior is not broken}
## Out of Scope
- {Explicitly block the temptation to expand}
## References
-
9.4 Agent delegation checklist (for a PR description or ticket comment)
## Pre-agent-delegation check
- [ ] All AC are checkboxes and verifiable
- [ ] Relevant code paths are specified in references
- [ ] The boundary is drawn with "out of scope"
- [ ] The existing pattern to follow is specified
- [ ] There is a way to verify the result (tests)
- [ ] Not a high-risk area (auth / payments / migration)
Label: {agent-ready / agent-assisted / human-only / needs-refinement}
9.5 Tips for running templates
- A template is a starting point, not a shackle. Delete fields you do not use, but keep the five components (context, problem, AC, constraints, references).
- The
{placeholder}guidance text inside a field should be deleted as the author fills it in. A{placeholder}submitted still empty is itself a signal that "this ticket is not yet finished." - Pin three examples of good tickets in the team wiki. New hires and agents alike use them as a reference point.
Epilogue — A Great Ticket Is an Investment Sent to the Future
The ticket is the highest-leverage written artifact in software. Write it well once and it pays out as long as the code it produced lives. Write it badly once and every downstream stage splits the cost — and in the AI era, that cost is doubled. Humans had a safety net of "you can always ask"; for agents, the ticket is everything.
But the conclusion is simple. Write it clearly for humans and an agent can handle it too. It is not a separate skill — it is double the reward on the same skill.
The great-ticket checklist
- Context — "why we are doing this now" is written in one paragraph
- Problem — written as an observable fact, not a symptom
- Acceptance criteria — a verifiable checkbox list
- One fact — no AC line bundles two with "and"
- Scope — sized to finish as one reviewable PR
- Out of scope — what you will not do this time is stated
- Constraints — what not to touch, the performance budget, is written
- References — relevant code paths, design, predecessor tickets are linked
- Traceability — epic, dependency tickets, PR are connected bidirectionally
- Labels — intent is signaled with
agent-ready/human-onlyand the like
Anti-patterns to avoid
- Vague verbs — turn "improve / optimize / clean up" into a measurable result
- Hidden scope — if "while we're at it" appears, split the ticket
- Missing AC — do not start work with no definition of "done"
- Multiple tasks — do not bundle several PRs' worth into one ticket
- Prescribing the solution — do not hide the problem and write only the solution
- Empty placeholders — do not submit with the template's guidance text undeleted
Next post teaser
The next article is "Writing a Great PR Description — How to Make Reviewers and Agents Approve in 5 Minutes." If the ticket is the input to the work, the PR description is the output. The craft of writing what changed and why, how it was verified, and where the reviewer should look first — and how that meshes with AI code reviewers too.
The 10 minutes it takes to write a great ticket prevents the 10 hours it takes to undo the wrong thing built. Those 10 minutes are an investment sent to the future team and the future you.