{"course":{"slug":"building-apps-with-ai","title":"Building Apps with AI","url":"https://addaly.com/learn/building-apps-with-ai","level":"beginner","track":"build"},"lesson":{"slug":"finding-the-change-that-broke-it","title":"Finding the change that broke it","url":"https://addaly.com/learn/building-apps-with-ai/finding-the-change-that-broke-it","position":31,"of":87,"minutes":8,"answer":"Answer when it last worked with a commit, then halve the range instead of reading every diff; handing over the culprit commit's diff beats describing the symptom.","keyIdea":"Answer when it last worked with a commit, then halve the range instead of reading every diff; handing over the culprit commit's diff beats describing the symptom."},"body":{"format":"markdown","text":"## The question that shortens everything\n\nWhen did this last work?\n\nIf you can answer that with a commit, you have converted an open-ended debugging problem into a search through a list, and the list is short. This is the practical return on committing at every working state, and it is why the git lesson sits where it does in this course.\n\n## Look at what changed\n\n```bash\ngit log --oneline -10          # the last ten save points\ngit diff HEAD~3                # everything since three commits ago\ngit log -p -- lib/queries.ts   # every change to one file, with diffs\n```\n\nThat third command is the one people do not know about and it is excellent. When one file is behaving strangely, its complete history with diffs is often enough on its own: you see the moment the condition changed and you see what it was before.\n\n## Bisecting by hand\n\nIf twenty commits sit between working and broken, do not read twenty diffs. Halve the range.\n\n```bash\ngit log --oneline -20        # find a commit from before the problem\ngit checkout a3f9c21         # go there, look at the app\n# works?    the bug is newer\n# broken?   the bug is older\ngit checkout main            # come back\n```\n\nFive checks find the culprit among thirty-two commits. Ten find it among a thousand. This is a large saving and it requires nothing but the discipline of small commits.\n\nGit will do the halving for you with `git bisect start`, `git bisect bad`, `git bisect good <commit>`, and then `git bisect good` or `git bisect bad` at each step until it names the commit. Worth learning once you have used the manual version enough to trust it.\n\n## The uncommitted case\n\nIf you have not committed for three hours, the bisect route is closed and you have one weaker option:\n\n```bash\ngit stash        # set aside everything since the last commit\n# test the app\ngit stash pop    # bring it all back\n```\n\nThat tells you whether the problem is in your uncommitted work or was there before. It does not tell you which part, because it is all one lump. Which is the lesson: the value of a commit is not the saving, it is the boundary.\n\n## What to do once you have found it\n\nYou now have a commit that introduced a bug. Three routes, in order of preference.\n\n**Read the diff and fix it.** Usually the diff is small and, knowing that it contains a bug, you will often spot it in under a minute. Ninety percent of the time this is the answer.\n\n> Figure: Halving the range instead of reading it. Line chart of Versions you have to open and try against Commits between the working one and the broken one: Read every diff (32, 32) (128, 128) (512, 512) (1024, 1024); Halve the range each time (32, 5) (128, 7) (512, 9) (1024, 10). Five checks find the culprit among thirty-two commits and ten among a thousand, because each check removes half of what is left. It costs nothing but the habit of small commits.\n\n**Revert it.** `git revert a3f9c21` creates a new commit undoing that one, leaving the history intact. Right when the change was not essential and you would rather have the working app back today.\n\n**Rebuild it.** If the change was a feature built through several rounds of patching, redo it from a cleaner description now that you know what went wrong. This is often faster than repairing, and it feels like defeat while being the cheapest option on the table.\n\n## Hand the culprit over, not the symptom\n\n```\nThis worked at commit a3f9c21 and is broken at 7e21b04.\nThe diff between them is:\n[paste git diff a3f9c21 7e21b04]\n\nThe symptom: bookings at exactly the end time of another\nbooking are now refused.\n\nWhich line in this diff causes that? Explain before changing anything.\n```\n\nThat is an enormously better request than a description of the bug, because it hands over the search space along with the problem. The model no longer has to guess where to look, and cannot wander into unrelated files while looking.\n\n## When nothing changed\n\nSometimes the code is identical and the behaviour is not. The variable is outside your repository: data that changed, a dependency that updated because a version was not pinned, an expired API key, a certificate, a third-party service having a bad day, or the clock: a bug that only appears after midnight UTC, or on the 31st, or when the month rolls over.\n\nWhen a bisect finds nothing, stop bisecting and go looking outside the code. Check your host's status page, look at the data the failing request touched, and check whether anything about it is new."},"check":{"question":"Bisecting finds that the bug exists in every commit including the oldest one you tested, yet the app worked yesterday. What does that mean?","options":[{"text":"The bisect was performed incorrectly and should be repeated.","correct":false,"tempting":"Distrusts a consistent result rather than reading it; the same answer at every commit is information, not an error."},{"text":"Something outside the repository changed: data, a dependency, an external service, or the date.","correct":true},{"text":"The bug was introduced in an even older commit that you have not reached.","correct":false,"tempting":"Would be the reading if the app had never worked; it did work yesterday on code that is in the tested range."},{"text":"The working version was never committed, so it does not appear in the history.","correct":false,"tempting":"Possible with uncommitted work, but that would make the newest commits broken rather than all of them equally."}]},"prev":{"slug":"classifying-the-failure","title":"Classifying the failure before you fix it","url":"https://addaly.com/learn/building-apps-with-ai/classifying-the-failure"},"next":{"slug":"dependency-trouble","title":"Dependency trouble, and the lock file","url":"https://addaly.com/learn/building-apps-with-ai/dependency-trouble"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Finding the change that broke it', in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/finding-the-change-that-broke-it (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Finding the change that broke it","courseTitle":"Building Apps with AI","module":{"index":4,"slug":"when-it-breaks","title":"When it breaks, and getting back"},"answer":null,"keyIdea":"Answer when it last worked with a commit, then halve the range instead of reading every diff; handing over the culprit commit’s diff beats describing the symptom.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"type":"line","title":"Halving the range instead of reading it","series":[{"label":"Read every diff","points":[[32,32],[128,128],[512,512],[1024,1024]]},{"label":"Halve the range each time","points":[[32,5],[128,7],[512,9],[1024,10]]}],"xLabel":"Commits between the working one and the broken one","yLabel":"Versions you have to open and try","caption":"Five checks find the culprit among thirty-two commits and ten among a thousand, because each check removes half of what is left. It costs nothing but the habit of small commits."},"alt":"Figure: Halving the range instead of reading it. Line chart of Versions you have to open and try against Commits between the working one and the broken one: Read every diff (32, 32) (128, 128) (512, 512) (1024, 1024); Halve the range each time (32, 5) (128, 7) (512, 9) (1024, 10). Five checks find the culprit among thirty-two commits and ten among a thousand, because each check removes half of what is left. It costs nothing but the habit of small commits."}},"blocks":[{"t":"h2","id":"the-question-that-shortens-everything","runs":[{"text":"The question that shortens everything"}]},{"t":"p","runs":[{"text":"When did this last work?"}]},{"t":"p","runs":[{"text":"If you can answer that with a commit, you have converted an open-ended debugging problem into a search through a list, and the list is short. This is the practical return on committing at every working state, and it is why the git lesson sits where it does in this course."}]},{"t":"h2","id":"look-at-what-changed","runs":[{"text":"Look at what changed"}]},{"t":"code","lang":"bash","label":"Bash","text":"git log --oneline -10          # the last ten save points\ngit diff HEAD~3                # everything since three commits ago\ngit log -p -- lib/queries.ts   # every change to one file, with diffs"},{"t":"p","runs":[{"text":"That third command is the one people do not know about and it is excellent. When one file is behaving strangely, its complete history with diffs is often enough on its own: you see the moment the condition changed and you see what it was before."}]},{"t":"h2","id":"bisecting-by-hand","runs":[{"text":"Bisecting by hand"}]},{"t":"p","runs":[{"text":"If twenty commits sit between working and broken, do not read twenty diffs. Halve the range."}]},{"t":"code","lang":"bash","label":"Bash","text":"git log --oneline -20        # find a commit from before the problem\ngit checkout a3f9c21         # go there, look at the app\n# works?    the bug is newer\n# broken?   the bug is older\ngit checkout main            # come back"},{"t":"p","runs":[{"text":"Five checks find the culprit among thirty-two commits. Ten find it among a thousand. This is a large saving and it requires nothing but the discipline of small commits."}]},{"t":"p","runs":[{"text":"Git will do the halving for you with "},{"text":"git bisect start","code":true},{"text":", "},{"text":"git bisect bad","code":true},{"text":", "},{"text":"git bisect good <commit>","code":true},{"text":", and then "},{"text":"git bisect good","code":true},{"text":" or "},{"text":"git bisect bad","code":true},{"text":" at each step until it names the commit. Worth learning once you have used the manual version enough to trust it."}]},{"t":"h2","id":"the-uncommitted-case","runs":[{"text":"The uncommitted case"}]},{"t":"p","runs":[{"text":"If you have not committed for three hours, the bisect route is closed and you have one weaker option:"}]},{"t":"code","lang":"bash","label":"Bash","text":"git stash        # set aside everything since the last commit\n# test the app\ngit stash pop    # bring it all back"},{"t":"p","runs":[{"text":"That tells you whether the problem is in your uncommitted work or was there before. It does not tell you which part, because it is all one lump. Which is the lesson: the value of a commit is not the saving, it is the boundary."}]},{"t":"h2","id":"what-to-do-once-you-have-found-it","runs":[{"text":"What to do once you have found it"}]},{"t":"p","runs":[{"text":"You now have a commit that introduced a bug. Three routes, in order of preference."}]},{"t":"p","runs":[{"text":"Read the diff and fix it.","b":true},{"text":" Usually the diff is small and, knowing that it contains a bug, you will often spot it in under a minute. Ninety percent of the time this is the answer."}]},{"t":"p","runs":[{"text":"Revert it.","b":true},{"text":" "},{"text":"git revert a3f9c21","code":true},{"text":" creates a new commit undoing that one, leaving the history intact. Right when the change was not essential and you would rather have the working app back today."}]},{"t":"p","runs":[{"text":"Rebuild it.","b":true},{"text":" If the change was a feature built through several rounds of patching, redo it from a cleaner description now that you know what went wrong. This is often faster than repairing, and it feels like defeat while being the cheapest option on the table."}]},{"t":"h2","id":"hand-the-culprit-over-not-the-symptom","runs":[{"text":"Hand the culprit over, not the symptom"}]},{"t":"code","lang":"","label":"Text","text":"This worked at commit a3f9c21 and is broken at 7e21b04.\nThe diff between them is:\n[paste git diff a3f9c21 7e21b04]\n\nThe symptom: bookings at exactly the end time of another\nbooking are now refused.\n\nWhich line in this diff causes that? Explain before changing anything."},{"t":"p","runs":[{"text":"That is an enormously better request than a description of the bug, because it hands over the search space along with the problem. The model no longer has to guess where to look, and cannot wander into unrelated files while looking."}]},{"t":"h2","id":"when-nothing-changed","runs":[{"text":"When nothing changed"}]},{"t":"p","runs":[{"text":"Sometimes the code is identical and the behaviour is not. The variable is outside your repository: data that changed, a dependency that updated because a version was not pinned, an expired API key, a certificate, a third-party service having a bad day, or the clock: a bug that only appears after midnight UTC, or on the 31st, or when the month rolls over."}]},{"t":"p","runs":[{"text":"When a bisect finds nothing, stop bisecting and go looking outside the code. Check your host’s status page, look at the data the failing request touched, and check whether anything about it is new."}]}],"check":{"question":[{"text":"Bisecting finds that the bug exists in every commit including the oldest one you tested, yet the app worked yesterday. What does that mean?"}],"options":[{"text":[{"text":"The bisect was performed incorrectly and should be repeated."}],"correct":false,"tempting":[{"text":"Distrusts a consistent result rather than reading it; the same answer at every commit is information, not an error."}]},{"text":[{"text":"Something outside the repository changed: data, a dependency, an external service, or the date."}],"correct":true,"tempting":null},{"text":[{"text":"The bug was introduced in an even older commit that you have not reached."}],"correct":false,"tempting":[{"text":"Would be the reading if the app had never worked; it did work yesterday on code that is in the tested range."}]},{"text":[{"text":"The working version was never committed, so it does not appear in the history."}],"correct":false,"tempting":[{"text":"Possible with uncommitted work, but that would make the newest commits broken rather than all of them equally."}]}],"order":[2,0,3,1],"why":null},"prev":{"slug":"classifying-the-failure","title":"Classifying the failure before you fix it","minutes":8},"next":{"slug":"dependency-trouble","title":"Dependency trouble, and the lock file","minutes":8}}}