Yasin Hassim
All posts
· 9 min read ·Updated 20 October 2026

How I Actually Work With AI on a Live Production System

Not a toy project: a years-old system with real clients and real data. The full lifecycle I run with Claude, from a new ticket to a merged batch, the change-request paperwork, and the rules file that came out of real mistakes.

AIDevelopmentWorkflow

Live production system Public-sector client Claude Code Updated October 2026
TL;DR
  • The AI does the legwork: reading code, scoping work, building, testing against real data, drafting every update.
  • Every write that other people see waits for my explicit yes: creating tickets, commits, tracker updates, messages.
  • A rules file carries what I've corrected between sessions. Most of its rules exist because something went wrong once.
  • Result: one sitting took a large ticket from "new" to four subtasks in review, without the quality dropping.

Most AI-Coding Stories Are Toy Examples

Ask an AI to build a todo app from scratch and it looks impressive in a screen recording. That’s not the situation most working developers are in. I spend most of my time on a case-management platform used by a public-sector client: years of accumulated modules, dashboards and report exports, other people’s code, real cases with real people’s data behind them, and a project process that expects logged, reviewable work rather than “it works on my machine.” Nothing about that project gets rebuilt clean. It has to be understood and extended carefully.

Here’s what that looks like with an AI assistant in the loop. Not a demo, the day-to-day.

One Session, Replayed

The clip below reenacts a real session from this month, with names and identifiers changed. A ticket asked for PDF export across 18 report tables. Watch the moments where the session stops and waits.

Reenactment of a real session, names changed. The purple prompts are the gates: nothing past them happens without my "yes".

That’s the whole idea in 26 seconds. The rest of this post is the process behind it.

The Lifecycle, End to End

Work comes in through the team’s issue tracker, never from a prompt I invent. Over a few months the session settled into a fixed lifecycle. It isn’t Claude’s default behavior; it’s a routine I established and corrected until it stuck.

BUILD THE WORK 1 · TICKET from the tracker 2 · INVESTIGATE code + real data first 3 · SCOPE draft subtasks waits for my yes 4 · BUILD inside the ticket only 5 · VERIFY against real data SHIP THE WORK 6 · COMMIT one per repo per task waits for my yes 7 · TRACKER time · notes · status waits for my yes 8 · IN REVIEW tasks pile up 9 · BATCH MR one per repo 10 · SENIOR reviews + merges 11 · DONE closed by me = a gate: the AI drafts it, shows me exactly what it will write, and stops
Eleven stages, three gates. Between gates the AI reads, builds and drafts; everything other people will see passes through a gate.

Ticket and investigation. I start a session with one line: check out this ticket. The AI pulls the issue, its subtasks and its comment history, then tells me what’s done, what’s blocked and what’s left. Before it proposes anything, it reads the code and queries real data. “The report probably uses the same header layout as the others” is a guess. “Here are the 18 export classes and how each one lays out its headers” is a fact.

Scoping. Big tickets get split. The AI drafts subtasks with titles, files and acceptance notes, and the draft sits in the chat until I approve it. It only creates them in the tracker after I say so, assigned to me. That rule exists because an early session assigned a ticket to the wrong person.

Build and verify. The build stays strictly inside the ticket. Verification means running the new code against real data, not “it compiles.” In the replayed session that meant comparing column counts and total rows of each new PDF against the existing Excel export, rendering the PDFs to look at them, and checking the empty-data case.

Commit and tracker. Commits use the ticket code as their scope (feat(#143): …), one per repository per task, and only when I say commit. Closing a task means logging time (I give the number; it never guesses), posting the commit references, appending a short resolution to the description and moving the status. All of that is drafted, shown to me in full, and written only after a yes.

Review and release. Finished tasks collect in review. When I’m ready, the AI prepares one merge request per repository for the whole batch: a short table of tasks, plus notes on anything that needs care at deploy time (migrations, config, new packages). Per-task detail already lives on each ticket, so the merge request stays readable even with a dozen tasks in it. Once I’ve opened the merge requests, it writes the message to my senior and, after another yes, replies on each ticket with its merge request link so every ticket traces to the code that closed it.

One Ticket, One Sitting

Here’s what the replayed session added up to. These numbers are from the real session.

WhatCount
Report tables that got PDF export18
Subtasks drafted, approved, then created4
Commits (backend + frontend, per subtask)8
Shared PDF templates reused, not rebuilt1
Existing bugs found along the way3
Existing bugs fixed along the way0

That last pair is deliberate. While building, the AI found three bugs that already existed: a data class that crashed on one filter mode, a report title with the wrong code in it, and a year filter that didn’t narrow three reports at all. It reported all three in the ticket and fixed none of them. They’re outside the ticket, and teammates reviewing my merge request shouldn’t have to ask why a PDF task touched an unrelated query. Each one can become its own ticket.

It also corrected itself. Mid-session it flagged one report as returning all zeros. A few steps later it noticed the test had passed the year in the wrong unit, re-ran it, and told me plainly that the earlier claim was wrong. That’s the behavior I want: a wrong claim retracted in the next message, not quietly left in the record.

Change Requests: When the Work Needs Paperwork

Some tickets aren’t bugs. They’re changes to what the system does, and the client signs off on those with an official change request form. Filling that form by hand for each one was slow, so it’s now part of the flow.

CR TICKET marked [CR] FORM GENERATED script fills the official PDF checked before sharing CLIENT SIGNS requester fields are theirs NORMAL LIFECYCLE investigate → … → done
The form is generated from the ticket, so the paperwork and the work describe the same change.

When I ask for a change request, the AI reads the ticket and adds an entry to a small JSON file. A script stamps it onto the client’s official two-page form: module, date requested, and a description that’s one lead sentence plus two to four bullets so it fits the box. The AI renders the first page and looks at it before handing it over. The requester’s name, signature and approval sections stay blank. Those belong to the client, and filling them in for them would be presumptuous at best.

The content conventions came from the forms already approved before this existed: English descriptions that keep the system’s own screen names, dates in the client’s format, file names that start with the ticket code. Copying what already got signed beats inventing a style.

The Rules File: Lessons That Stick

The part that changes the experience most isn’t any single fast fix. It’s that corrections persist. When I correct the AI, the correction goes into a small rules file that loads at the start of every session. Picking a task back up doesn’t mean re-explaining the system, or re-learning a lesson the hard way.

the next time the same situation comes up SOMETHING SLIPS in a real session I CORRECT IT and say why RULE SAVED rule + why + how to apply one fact per file NEXT SESSION starts with it loaded
Each rule keeps the reason it exists. A rule without its reason gets applied too literally or quietly dropped.

Almost every rule has a story behind it:

RuleWhy it exists
Show me every tracker create or update before writing itA ticket was once assigned to the wrong teammate because an ID was assumed, not checked
Check the test database is isolated before running feature testsOne test run reset the local development database. Recovering it took hours
Stay inside the ticket. Report unrelated bugs, don't fix themTeammates pushed back on review when changes showed up that nobody asked for
Ask me for logged time, never estimate itTime logs are a record other people rely on, so they have to come from me
Follow the known-good pattern for report exportsA report silently hid its first two data rows because it built its headers differently from every other report
Write internal notes in English, client-facing text in the client's languageTickets are read by the whole team; screens and letters are read by the client

The rules file also holds process: the lifecycle above, the commit format, the batch merge request template, the status-update format I send to the team. “Close this task” now means the same steps every time, in the same order, with the same confirmation before anything is written.

Two Examples Where the Discipline Paid Off

A silently wrong report. One export had been quietly hiding its first two rows of real data. Not a crash, just a layout bug in how its header rows were inserted, different from the pattern every other report used. Nobody had noticed because nothing errored. Walking through the export logic and comparing it with a known-good report found the mismatch in minutes instead of a long manual trace. The fix became a rule, so the next report starts from the right pattern.

A leak that had already happened. An office coordinator mentioned, almost in passing, that someone outside a case had seen details they shouldn’t have. That’s a very different bug to chase than “the button doesn’t work.” It meant systematically testing every role, every office-assignment state and every view against real data instead of guessing at what the access rules probably were. That turned up two separate gaps, not one, including a second that nobody had reported yet. Both were scoped and fixed the same day, shaped by how the client actually wanted access to work rather than patched until the symptom went away.

Neither came from “AI writes code fast.” They came from being able to check every case methodically, instead of the two or three a rushed manual review would cover.

What Hasn’t Changed

The client conversations, the trade-off calls, what ships and when: that’s all still mine, and it should stay that way. The gates make that concrete. The AI can read anything, draft anything and build inside the ticket, but nothing reaches the tracker, the repository or another person until I’ve seen exactly what it will say and said yes.

What’s different is how much of the surrounding work I can hand off and actually trust: investigation, exhaustive testing, paperwork, the release notes nobody enjoys writing. That’s what lets me take on more at once without the quality dropping, which was the whole point.

Keep reading