So, I don't know about you guys, but these days I barely write any of the code myself. My agent does most of it. And to be honest, it generally feels pretty good. But how do I know that what is getting written is actually good code? You know, is it good proper convex code? Well, one really good way of finding that out is by layering on as many deterministic code quality tools on as possible, such as [music] ESLint and in particular the Convex ESLint plugin, which just got a big upgrade thanks to version 5. If you aren't familiar, ESLint basically just reads through your code and flags anything that breaks a set of rules. [music] And the ComX plugin for ESLint basically just adds a bunch of rules that are specific to Convex. Oh, and by the way, you might have noticed that I look a little bit different today. I'm trying out something new with this one. So, what do you think? Do you like it? Sure, it's not the real me, but [music] the real me can't do this. Anyways, where was I? Now, thanks to the AI files, we already give your agent a bunch of guidelines on how to write convex properly, [music] and they definitely do help. But yeah, agents don't always listen, especially once the conversation gets long and your guidelines are like way off the top of the con the context somewhere. ESLint on the other hand runs all the time and every time your agent runs uh lint and so when something is wrong, it tells the agent exactly what to [music] do instead. Setting it up is super easy. You literally just point your agent at the comics docs page for the ESLint plugin and it'll just do it all for you. [music] It works with the older ESLint 8 style config 2 and even Oxalint if that's your thing. And by default, it only looks for the files in your convex directory, so it won't go complaining about the rest of your app. All right, let's dig in a bit now to have a look at some of the specific things that it's going to [music] catch for us. First up, and this is one of the new ones in version 5. If your agent uses process end still in your convex code, this will [music] get flagged. That's because earlier this year, Comics came out with a much better typed environment variables object system. So this rule is going to make sure that you're using that new system instead. Nice. Next up is db.get. So there's a new API here as well that the agent should be using. So if it leaves off the table name, the plug-in will add it in [music] for you. And if your agent is writing the old style of comics function too, that's going to get fixed up [music] as well. Now, this is one I used to see agents doing a lot and to be fair is something I have definitely done myself in the past as well, but it's using dot filter on queries. And it's fine when you know your table is tiny and it has not [music] a lot of data to filter through, but as your table grows larger and larger, it's going to have to read more and more data into the query to be able to filter it. So, the solution, as we probably know, is to [music] use indices. Now, this plugin can't fix that directly. It can't tell you exactly what index to use, but it will warn your agent. So, it can go out and swap it for a more appropriate index. Oh, and if your agent forgets the args validator on a function, [music] that's going to be an error as well as it's obviously pretty important cuz you don't want people to send whatever data to your function. Oh, and a new rule that was added in version 5 is this duplicate indexes rule. So, the way this works is that if your agent adds an index on a user and date when you already have one on just the user, the plug-in will point out that the first one is now redundant as the second one can already do everything that that does and more. And it's always a good idea to keep the number of indices that you're running low so that it doesn't take up more database memory than it needs. Oh, and here's a fun one. If your agent schedules a cron to run exactly at the top of the hour, it's going to get a warning. That's because loads of apps schedule their stuff to run exactly at the top of the hour because this becomes like the busiest moment on the clock. It means [music] it makes it quite hard to balance and manage Convex's traffic's behind the scenes. And so if your agent can just kind of pick slightly different minute before or after, then your app and the poor Convex infraine are going to be a little bit happier. Now, most of these rules are on by default, but there are a couple that are off, and this is one of those. It's called require access control, and it makes sure that every public function that your agent writes checks who is actually going to be calling it. So, basically, it's going to enforce that inside of your mutation or query, it's going to have something like require user, which means that it's going to protect that function from being called from the outside. Otherwise, it really should just be an internal mutational query or action instead. The way it works is a little bit simplistic for now. It just looks for specific names like require or has. So, require user or has ro. But you can change that pattern if you're using something else. And it comes off by default because probably it would trigger too many warnings in existing apps. So, it would be too much of a breaking change. But I would say you probably should enable this as it's worth having more protection than less. So I really like this ESL and I think it works well is because the messages that it that the plug-in gives to your agent don't just say that something is wrong. [music] It also tells them what to do to fix it instead. And generally these fixes will happen before you even are aware of them, [music] which is very nice if you ask me. I really like these ESLint plugins and I think we're definitely going to be adding more of them [music] over time. Every time we see agents making the same comics mistakes over and over, I think that's going to be a pretty good candidate for a new rule. [music] So hopefully over time this becomes a really nice way to make sure your agents are writing, you know, proper comics code. So with that in mind, what comics mistake do you keep seeing your agents make? [music] Drop me a comment down below and I'll pass it on to Nicholas. Hello. Who's looking after the ESLint plugin and who knows, maybe it will end up in the next Comics rule. Anyways, I think that's all I've got for you guys for now. Don't forget to drop me a like and sub. And until next time, thanks for watching. Cheerio.
So, I don't know about you guys, but these days I barely write any of the code myself. My agent does most of it, and to be honest it generally feels pretty good. But how do I know that what is getting written is actually good code? You know, is it good proper Convex code?
Well, one really good way of finding that out is by layering on as many deterministic code quality tools as possible, such as ESLint, and in particular the Convex ESLint plugin, which got a big upgrade with version 5.
If you aren't familiar, ESLint basically just reads through your code and flags anything that breaks a set of rules, and the Convex plugin adds a bunch of rules that are specific to Convex. Here it is running over a little demo app that my agent made, with a bunch of deliberate mistakes in it:
0:00 / 0:00
So that's 8 problems, in code that deploys just fine.
Oh and by the way, if you watch the video version of this you'll notice I look a little bit different. I was trying out something new with that one, and the whole thing is a cartoon version of me. Sure it's not the real me, but the real me can't do this:
0:00 / 0:00
Anyways, where was I?
Guidelines vs ESLint
Now, thanks to the AI files, we already give your agent a bunch of guidelines on how to write Convex properly, and they definitely do help. But agents don't always listen, especially once the conversation gets long and your guidelines are way off the top of the context somewhere.
ESLint on the other hand runs every time your agent runs lint, and when something is wrong it tells the agent what to do instead. That does mean your agent has to actually run it, so it's worth telling it to in your AGENTS.md or CLAUDE.md.
Setting it up
Setting it up is super easy. You literally just point your agent at the Convex docs page for the ESLint plugin and it'll do it all for you. You should end up with something like this, which is the config from that demo app:
Simple enough, right? The typescript-eslint part is what lets ESLint read your TypeScript, and it also lets the plugin look at your types, which matters for one of the fixes in a minute.
It works with the older .eslintrc.js style config too, and even Oxlint if that's your thing. By default it only looks at the files in your convex directory, so it won't go complaining about the rest of your app.
Someone in the comments on the video asked about Biome. Biome can't load ESLint plugins, so it can't run these rules by itself, but you can run them alongside it with Oxlint or ESLint on just your convex directory.
What it catches
All right, let's dig in a bit now and have a look at some of the specific things it's going to catch for us. The editor clips in this post are all from that same demo app.
First up, and this is one of the new ones in version 5, if your agent uses process.env in your Convex code this will get flagged. That's because earlier this year Convex came out with a much better typed environment variables system, so this rule makes sure you're using that instead. Keep an eye on line 7:
0:00 / 0:00
Noice! npx eslint --fix swaps it for env.OPENAI_API_KEY and adds the import up on line 1. It can only do that by itself when the variable is already declared in your convex.config.ts, as it is here. If it isn't declared you get offered the same change as a suggestion in your editor instead.
db.get and the old function syntax
Next up is db.get. There's a newer API here that your agent should be using, so if it leaves off the table name, the plugin will add it in for you. This one's on line 7 as well:
0:00 / 0:00
This is the fix that needs those types from earlier, as that's how it knows the ID belongs to tasks. In Oxlint you still get told about it, but it can't fill in the name for you.
And if your agent is writing the old style of Convex function, that's going to get fixed up as well:
0:00 / 0:00
Also really good. The real fix does leave the indentation a bit wonky tho, so let your formatter have a go at it afterwards.
Using .filter on queries
Now, this is one I used to see agents doing a lot, and to be fair it's something I have definitely done myself in the past as well 😅 It's using .filter on queries. It's fine when you know your table is tiny and there's not a lot of data to filter through, but as your table grows larger and larger it's going to have to read more and more data into the query to be able to filter it.
So the solution, as we probably know, is to use indexes. The plugin can't fix this one directly as it can't tell you exactly which index to use, but it will warn your agent so it can go and swap it for a more appropriate one. Here's the warning on line 23, and then that line getting swapped for .withIndex:
0:00 / 0:00
Args validators and duplicate indexes
Oh and if your agent forgets the args validator on a function, that's going to be an error as well. It's obviously pretty important, because you don't want people to send whatever data they like to your function:
A mutation with no args validator, underlined in red, with the message: Convex function is missing args validator but has parameter. Add appropriate args validator.
Another new rule in version 5 is duplicate indexes. The way this works is that if your agent adds an index on user and date when you already have one on just the user, the plugin will point out that the first one is now redundant, as the second one can already do everything that it does and more. So here by_user gets flagged, and then it gets deleted:
0:00 / 0:00
It's always a good idea to keep the number of indexes you're running low, as each one slows down your writes and takes up storage. The one catch, which the message points out, is the sort order. by_user sorts the matching rows by _creationTime and by_user_and_date sorts them by date, so if you really need the first one you can keep it and disable the rule on that line.
Crons at the top of the hour
Oh and here's a fun one. If your agent schedules a cron to run exactly at the top of the hour, it's going to get a warning. That's because loads of apps schedule their stuff to run exactly at the top of the hour, so it becomes the busiest moment on the clock, and that makes it quite hard to balance and manage Convex's traffic behind the scenes. It's a bit like everyone piling through one door at 12 o'clock while one guy strolls through the other:
0:00 / 0:00
So if your agent can just pick a slightly different minute before or after, then your app and the poor Convex infra engineers are going to be a little bit happier. In this one the 0 in "0 * * * *" gets the warning, and then it gets changed to 17:
0:00 / 0:00
If you read that message you'll see there's an even easier way, which is to use the helper and not give it a minute at all, then Convex picks one for you and spreads the runs across the hour:
Now, most of these rules are on by default, but there are a few that are off, and this is one of those. It's called require-access-control, and it makes sure that every public function your agent writes checks who is actually going to be calling it.
You turn it on by adding this after the recommended config:
In this clip that rule goes in first, then we flip over to users.ts, where deleteUser goes red with a message that starts "Anyone on the internet can call this Convex function". Adding a call to requireUser makes it go away:
0:00 / 0:00
So basically it's going to enforce that inside your query, mutation or action there's something like requireUser. Otherwise it really should just be an internalMutation, internalQuery or internalAction instead, as those can't be called from the outside.
The way it works is a little bit simplistic. It just looks for function names starting with require, assert, check, ensure, can or has, so requireUser or hasRole, but you can change that pattern if you're using something else. It can't tell if it's the right check for that function either, and it doesn't look at HTTP actions or custom functions.
I think it's off by default because it would probably trigger too many warnings in existing apps, so it would be too much of a breaking change. But I would say you probably should enable this, as it's worth having more protection than less.
Why it works so well with agents
So why I really like this with agents is that the messages the plugin gives them don't just say that something is wrong, they generally say what to do about it too. Here's what your agent gets for that duplicate index when it runs npx eslint .:
1convex/schema.ts
214:6 error Index "by_user" indexes the start of index "by_user_and_date"(["userId", "dueDate"]), which answers the same queries. Every index slows down writes and adds storage, so delete "by_user" and query "by_user_and_date" instead. Keep both only if you need the different sort order: "by_user" sorts matching rows by `_creationTime`, "by_user_and_date" sorts them by "dueDate". To keep it, add`// eslint-disable-next-line @convex-dev/no-duplicate-indexes` @convex-dev/no-duplicate-indexes
3
That's pretty much everything it needs to go and sort it out by itself. And generally these fixes will happen before you're even aware of them, which is very nice if you ask me.
Conclusion
So just to recap: guidelines definitely help but agents don't always listen, and lint flags the mistake every time it runs. Point your agent at the docs to get it set up, and I'd turn on require-access-control while you're there.
I really like these ESLint plugins. Every time we see agents making the same Convex mistakes over and over, I think that's a pretty good candidate for a new rule, and hopefully this becomes a really nice way to make sure your agents are writing, you know, proper Convex code.
A massive thanks to Nicolas, who looks after the plugin.
Until next time, cheerio! 👋
All gas, no breakages
Convex is the reactive backend platform that keeps up with you and your agents. Database, functions, workflow, sync, search, file storage, and more. All TypeScript, zero glue.