{
    "componentChunkName": "component---src-pages-blog-mdx-slug-js",
    "path": "/blog/2026-05-30-outsmarting-github-copilot/",
    "result": {"data":{"mdx":{"frontmatter":{"title":"I Tried to Outsmart Copilot. It Made Me a Better Developer.","tags":["programming","code review","AI","Claude code"],"categories":[],"date":"May 30, 2026","image":null,"imageAlt":null},"id":"8bc48193-2ba7-5463-9edb-0f5e3a910bb1","body":"var _excluded = [\"components\"];\nfunction _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n/* @jsxRuntime classic */\n/* @jsx mdx */\n\nvar _frontmatter = {\n  \"title\": \"I Tried to Outsmart Copilot. It Made Me a Better Developer.\",\n  \"date\": \"2026-05-30T00:00:00.000Z\",\n  \"tags\": [\"programming\", \"code review\", \"AI\", \"Claude code\"],\n  \"categories\": [],\n  \"draft\": false\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n    props = _objectWithoutProperties(_ref, _excluded);\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"When my team enabled Copilot's automated code review, I thought it would be a time-saver. It wasn't. At least not at first.\"), mdx(\"p\", null, \"The pattern was the same every time: I'd finish a feature, feel good about it, open a pull request, and within seconds Copilot would leave eight, nine, ten comments. Fine. I'd work through them, push again, and get a fresh batch. The frustration wasn't that the feedback was wrong. It was the whiplash. The feeling of constantly reacting, never getting ahead of it. It started to feel less like a code review and more like a game I kept losing.\"), mdx(\"p\", null, \"So I decided to stop playing defense.\"), mdx(\"hr\", null), mdx(\"h2\", null, \"Building the Cheat Sheet\"), mdx(\"p\", null, \"My first instinct was simple: if I knew what Copilot was going to say, I could fix it first. I started keeping notes. After a few weeks I had a mental model of the categories: N+1 queries, missing authorization checks, edge cases on nil returns, that kind of thing. But a mental model isn't something you can run.\"), mdx(\"p\", null, \"I use Claude for a lot of my day-to-day development, and it has a feature called \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview\"\n  }, \"skills\"), \", essentially a markdown file that instructs Claude how to approach a specific task. I started writing one for pre-PR review.\"), mdx(\"p\", null, \"The skill works by diffing the current branch against a base ref, scanning the changed files, and running through a checklist of categories: multi-tenancy violations, N+1 queries, authorization gaps, missing edge case handling, anti-patterns, test coverage, and more. It reads the project's actual conventions first: which auth library you're using, whether you have soft deletes, how your background jobs are named. That way it gives you feedback that's relevant to \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"your\"), \" codebase, not a generic Rails app.\"), mdx(\"p\", null, \"The output is a triage list: \\uD83D\\uDD34 must fix before PR, \\uD83D\\uDFE1 should fix, \\uD83D\\uDFE2 consider.\"), mdx(\"hr\", null), mdx(\"h2\", null, \"The Iteration Loop\"), mdx(\"p\", null, \"The first version wasn't very good. It would surface things Copilot didn't care about and miss things it consistently flagged. So I ran them side by side, my skill then Copilot, and treated the gaps as bugs.\"), mdx(\"p\", null, \"Over several PRs, patterns emerged. Copilot was especially aggressive about:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"N+1s in serializers and presenters, not just controllers\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Strong parameter gaps, including nested attributes\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"fetch\"), \" calls in React that didn't check \", mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"response.ok\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Async state race conditions: snapshotting state before an \", mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"await\"), \", then clearing it unconditionally on success, wiping entries that arrived during the in-flight request\")), mdx(\"p\", null, \"That last one, I'll be honest, I had to look it up the first time Copilot flagged it. Once I understood it, I started seeing it everywhere. The skill now has an explicit check for it:\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"When a function snapshots state before an \", mdx(\"code\", {\n    parentName: \"strong\",\n    \"className\": \"language-text\"\n  }, \"await\"), \", then clears state unconditionally on success, it wipes entries that were queued during the in-flight request. The correct pattern is a functional update that removes only the snapshotted entries.\"))), mdx(\"p\", null, \"I never would have written that check if Copilot hadn't flagged the pattern three times in two weeks.\"), mdx(\"hr\", null), mdx(\"h2\", null, \"What Actually Changed\"), mdx(\"p\", null, \"Here's the unexpected part: I thought the goal was to silence Copilot. That stopped being the goal pretty quickly.\"), mdx(\"p\", null, \"Running the skill before opening a PR became a habit, and the habit changed how I write code. I started thinking about nil guards before I finished the method. I started thinking about race conditions before I'd even wired up the submit button. The skill didn't just catch mistakes. It made the checklist part of how I think.\"), mdx(\"p\", null, \"Copilot still leaves comments. But now it's usually one or two things I may have missed or consciously decided to defer, not a wall of feedback I didn't see coming. The conversation feels different. More like a second opinion than a verdict.\"), mdx(\"hr\", null), mdx(\"h2\", null, \"The Skill Is Still a Work in Progress\"), mdx(\"p\", null, \"It's not perfect. It occasionally surfaces false positives, particularly around performance concerns where it can't know the actual query plan or data volume. I've tried to bake in some conservatism there: performance findings that can't be verified structurally go in the \\uD83D\\uDFE2 category with language like \\\"worth profiling against a large tenant before merging,\\\" rather than being flagged as blockers.\"), mdx(\"p\", null, \"It also requires reading the project before reviewing, which means the first run on a new codebase takes a moment longer while it scans the auth layer, Gemfile, and job conventions. That's intentional. I'd rather have relevant feedback than fast feedback.\"), mdx(\"p\", null, \"The skill lives in our company's internal Claude marketplace, so I can't share it directly. But if you use Claude Code, writing something like it isn't as hard as it sounds. Start with the categories that hurt you most and go from there.\"), mdx(\"hr\", null), mdx(\"p\", null, \"The irony of the whole thing is that Copilot, the tool I was trying to get around, ended up teaching me most of what the skill knows. I just gave it a better memory than I have.\"));\n}\n;\nMDXContent.isMDXComponent = true;","timeToRead":3,"slug":"2026-05-30-outsmarting-github-copilot"},"allMdx":{"edges":[{"next":{"slug":"2013-03-17-silicon-valley-devfest/","frontmatter":{"title":"Silicon Valley DevFest"},"id":"edcdad98-0f1c-5f08-b9bf-ef2584a6784d"},"previous":null,"node":{"id":"3378703f-653c-58fd-a11e-7adc675fb79e","frontmatter":{"title":"Makings of a Pythonista"},"slug":"2012-11-21-makings-of-a-pythonista"}},{"next":{"slug":"2015-06-14-in-search-of-a-faster-query","frontmatter":{"title":"In search of a faster query"},"id":"c6497654-5ee0-5b14-b735-e08bbbc6a4bf"},"previous":{"frontmatter":{"title":"Makings of a Pythonista"},"slug":"2012-11-21-makings-of-a-pythonista"},"node":{"id":"edcdad98-0f1c-5f08-b9bf-ef2584a6784d","frontmatter":{"title":"Silicon Valley DevFest"},"slug":"2013-03-17-silicon-valley-devfest/"}},{"next":{"slug":"2015-11-01-diary-of-a-dev-joys-of-building","frontmatter":{"title":"Diary of a Junior Dev: The joys of building"},"id":"ca6c84a6-3aea-5278-bf26-267d43acb227"},"previous":{"frontmatter":{"title":"Silicon Valley DevFest"},"slug":"2013-03-17-silicon-valley-devfest/"},"node":{"id":"c6497654-5ee0-5b14-b735-e08bbbc6a4bf","frontmatter":{"title":"In search of a faster query"},"slug":"2015-06-14-in-search-of-a-faster-query"}},{"next":{"slug":"2016-12-24-working-with-d3-based-chart-library/","frontmatter":{"title":"TIL: Working with a D3 based chart library"},"id":"776dcc9f-2ac4-511a-a9c6-982c226e0478"},"previous":{"frontmatter":{"title":"In search of a faster query"},"slug":"2015-06-14-in-search-of-a-faster-query"},"node":{"id":"ca6c84a6-3aea-5278-bf26-267d43acb227","frontmatter":{"title":"Diary of a Junior Dev: The joys of building"},"slug":"2015-11-01-diary-of-a-dev-joys-of-building"}},{"next":{"slug":"2017-03-15-react-conf-2017/","frontmatter":{"title":"React Conf 2017"},"id":"97c0196c-f0cd-5d6a-b2b9-fd3c6f1f1baa"},"previous":{"frontmatter":{"title":"Diary of a Junior Dev: The joys of building"},"slug":"2015-11-01-diary-of-a-dev-joys-of-building"},"node":{"id":"776dcc9f-2ac4-511a-a9c6-982c226e0478","frontmatter":{"title":"TIL: Working with a D3 based chart library"},"slug":"2016-12-24-working-with-d3-based-chart-library/"}},{"next":{"slug":"2017-04-12-the-initial-state-gotcha","frontmatter":{"title":"The Initial State Gotcha"},"id":"82041731-cb81-571d-958c-00f57d99b927"},"previous":{"frontmatter":{"title":"TIL: Working with a D3 based chart library"},"slug":"2016-12-24-working-with-d3-based-chart-library/"},"node":{"id":"97c0196c-f0cd-5d6a-b2b9-fd3c6f1f1baa","frontmatter":{"title":"React Conf 2017"},"slug":"2017-03-15-react-conf-2017/"}},{"next":{"slug":"2017-05-13-blue-socks-at-orange/","frontmatter":{"title":"Blue Socks Spotted at Orange"},"id":"893cf729-88c0-5706-bcb8-d14f75a30f2b"},"previous":{"frontmatter":{"title":"React Conf 2017"},"slug":"2017-03-15-react-conf-2017/"},"node":{"id":"82041731-cb81-571d-958c-00f57d99b927","frontmatter":{"title":"The Initial State Gotcha"},"slug":"2017-04-12-the-initial-state-gotcha"}},{"next":{"slug":"once-upon-a-blog/","frontmatter":{"title":"Once upon a Blog"},"id":"db5caf55-934b-5de1-aa6b-5a2078e4f869"},"previous":{"frontmatter":{"title":"The Initial State Gotcha"},"slug":"2017-04-12-the-initial-state-gotcha"},"node":{"id":"893cf729-88c0-5706-bcb8-d14f75a30f2b","frontmatter":{"title":"Blue Socks Spotted at Orange"},"slug":"2017-05-13-blue-socks-at-orange/"}},{"next":{"slug":"story-of-a-laptop-upgrade","frontmatter":{"title":"Story of a laptop upgrade"},"id":"91dabe95-0227-5d01-b1d1-f10959c5d43a"},"previous":{"frontmatter":{"title":"Blue Socks Spotted at Orange"},"slug":"2017-05-13-blue-socks-at-orange/"},"node":{"id":"db5caf55-934b-5de1-aa6b-5a2078e4f869","frontmatter":{"title":"Once upon a Blog"},"slug":"once-upon-a-blog/"}},{"next":{"slug":"the-code-we-write-today/","frontmatter":{"title":"The code we write today"},"id":"08584458-be98-53c2-8cf3-9fa51060e04b"},"previous":{"frontmatter":{"title":"Once upon a Blog"},"slug":"once-upon-a-blog/"},"node":{"id":"91dabe95-0227-5d01-b1d1-f10959c5d43a","frontmatter":{"title":"Story of a laptop upgrade"},"slug":"story-of-a-laptop-upgrade"}},{"next":{"slug":"rails-on-docker/","frontmatter":{"title":"Rails on Docker"},"id":"5e92911a-24f7-562d-8ecd-9a7391960489"},"previous":{"frontmatter":{"title":"Story of a laptop upgrade"},"slug":"story-of-a-laptop-upgrade"},"node":{"id":"08584458-be98-53c2-8cf3-9fa51060e04b","frontmatter":{"title":"The code we write today"},"slug":"the-code-we-write-today/"}},{"next":{"slug":"rails-on-docker-part-two/","frontmatter":{"title":"Rails on Docker Part Two"},"id":"534e725d-71be-5e6f-ace7-75db51185701"},"previous":{"frontmatter":{"title":"The code we write today"},"slug":"the-code-we-write-today/"},"node":{"id":"5e92911a-24f7-562d-8ecd-9a7391960489","frontmatter":{"title":"Rails on Docker"},"slug":"rails-on-docker/"}},{"next":{"slug":"super-powers-for-active-record-relation","frontmatter":{"title":"Super Powers for ActiveRecord::Relation"},"id":"3d5d8b08-cf43-57be-8c43-aad95e2dfd06"},"previous":{"frontmatter":{"title":"Rails on Docker"},"slug":"rails-on-docker/"},"node":{"id":"534e725d-71be-5e6f-ace7-75db51185701","frontmatter":{"title":"Rails on Docker Part Two"},"slug":"rails-on-docker-part-two/"}},{"next":{"slug":"2026-05-30-outsmarting-github-copilot","frontmatter":{"title":"I Tried to Outsmart Copilot. It Made Me a Better Developer."},"id":"8bc48193-2ba7-5463-9edb-0f5e3a910bb1"},"previous":{"frontmatter":{"title":"Rails on Docker Part Two"},"slug":"rails-on-docker-part-two/"},"node":{"id":"3d5d8b08-cf43-57be-8c43-aad95e2dfd06","frontmatter":{"title":"Super Powers for ActiveRecord::Relation"},"slug":"super-powers-for-active-record-relation"}},{"next":null,"previous":{"frontmatter":{"title":"Super Powers for ActiveRecord::Relation"},"slug":"super-powers-for-active-record-relation"},"node":{"id":"8bc48193-2ba7-5463-9edb-0f5e3a910bb1","frontmatter":{"title":"I Tried to Outsmart Copilot. It Made Me a Better Developer."},"slug":"2026-05-30-outsmarting-github-copilot"}}]}},"pageContext":{"id":"8bc48193-2ba7-5463-9edb-0f5e3a910bb1","slug":"2026-05-30-outsmarting-github-copilot","__params":{"slug":"2026-05-30-outsmarting-github-copilot"}}},
    "staticQueryHashes": ["2201243204"]}