Entropic Thoughts

Robot comment classifier

Robot comment classifier

Here’s a comment I read in some code I was working on.

Tagger walks the entire collection to locate flagged clusters. Skip it altogether when the cached, dirty-tracked flag count says there are none (the usual case): nothing needs marking, so the walk is pure waste. When the count is stale (e.g. right after an edit) or non-zero, we fall through to the real lookup, so stale marks never show. Same eventually-consistent signal the “Flags” badge relies on.

One thing in this comment caught my mind: the small parenthetical that says “the usual case”. If that were true, it would be important information! However, this comment is generated by a robot, which has no sense of what counts as the usual case in this domain. But by generating that comment, the robot tricks every future reader (both human and robot) into believing in a property of the system that does not exist.

Since I knew, in this case, that the comment was generated by a robot and that the property it contained was hallucinated, I could fix the comment. But that’s not always so easy. Here’s another comment.

AcmeRate’s live currency conversion (in our per-line totals) are incompatible with connection pooling: on a reused connection the rate lookup runs against the previous session’s locale and returns figures in the wrong currency. So fall back to a fresh connection when the order has a foreign-currency line (and therefore conversion), and keep pooling otherwise so large domestic orders stay fast. sessionInit runs before the request config is assembled, so localeState is already populated when this is read.

This comment implies that it is important that we “keep pooling” so that “large domestic orders stay fast”. If a human wrote this comment, I would assume they had thought carefully about it, and determined that the optimisation must be kept in. But if it’s written by a robot, it sounds more like it defensibly kept something in the code that it has no business deciding about, because it doesn’t know what it’s doing.1 The optimisation was useless. Only a little domain experience is needed to know that nearly every order contains a foreign currency, and large domestic orders are fast even without pooling.

The robot comment classifier

To aid my intuition, I wrote a classifier for source code comments. When fed the opening example of this article, it correctly predicts the comment was generated by a robot with almost certainty. The second example is also predicted to be robot-generated with very high confidence.

I wish at this point I could say “and it achieves 98 % accuracy!” like some of the llm detection papers out there do, but no. Some other spot checked examples include:

Provenance Classification Confidence
Robot Misclassified 53 %
Robot Correct 67 %
Robot Correct 78 %
Robot Correct 79 %
Robot Correct 92 %
Robot Correct 93 %
Robot Correct 99 %
Human Correct 55 %
Human Misclassified 71 %
Human Correct 74 %
Human Correct 85 %
Human Correct 90 %
Human Correct 96 %
Human Correct 97 %
Mixed Robot 54 %
Mixed Robot 74 %
Mixed Human 76 %
Mixed Human 86 %

As you can see, misclassifications happen often enough to show up in this short table, but it is encouraging that the misclassifications generally happen at lower confidence levels.2 The mixed-provenance comments were originally generated by a robot, but a human – that would be me – found the comments so terribly written they had to be rewritten. The rewritten comments retained some of the robot-generated structure, but they also have elements of human style. It makes sense that these comments don’t get super high confidence levels either.

I don’t think the accuracy can go beyond the roughly 80 % this model gets, for two reasons.

  1. Code comments are often short, around 20 words or less. There’s only so much signal that can be extracted from that little data.
  2. The training data is inaccuractely labeled. See the methodology notes further down for more on that.

This model is good enough to be an additional piece of evidence when I need it, but I wouldn’t trust it alone.

How do detect differences in language

The most interesting bit is probably not the classifier itself, but what it looks at to distinguish robot-generated comments from human ones. It’s worth knowing that while this is trained on a wide range of human authors, I suspect the vast majority of robot-generated comments in the training data come from Anthropic models, so the features we see below don’t really distinguish robots-in-general from humans as much as they distinguish Anthropic llm models from humans. This is an important point we’ll get back to later.

I’m not versed in computational linguistics, so for this project I did some cursory reading and picked up three basic ways to decompose texts to try to extract style differences. All of the methods I tried work by cutting up the source text into smaller fragments, counting how often those fragments appear in the text, and then seeing whether that frequency is higher or lower for robot-generated texts compared to human-written ones.

Character frequency (62 % accuracy)

The most basic way to cut up text is into characters, and this does carry useful signal. A naïve guess, for example, would be that em dashes () signify a comment was generated by a robot. That’s true! Other such typographical marks robots produce more of than humans are semicolons (;), unicode arrows (), and ellipses ().

Character frequency analysis also tells us robots generate more syntactically complex comments than humans: they contain more full stops (.), commata (,), parentheses (( and )), and line breaks. Robots also surround literal strings with straight, ascii quotation marks (") more than humans do, e.g. to reference text in user interfaces. In general, robots use a wider variety of symbols than humans, and produce text with a higher symbol density.

There are two groups of symbols that appear more often in human text:

  • Colons (:) as separators introducing the next part, as I did in the line leading up to this list.
  • Symbols that come from code or parts of code included in comments, e.g. dollar signs ($), less-than and greater-than signs (< and >), backticks (`), equals signs (=), exclamation marks (!), square brackets ([ and ]).

Robots are less likely to include code in comments than humans are, and also less likely to write TODO comments.

Common word frequency (64 % frequency)

The next level up in language abstraction might be words. However, at that point we have to be careful. Due to the way language works (with documents often containing unique words), there’s a risk a word-frequency based classifier learns differences in subject matter rather than style. To avoid subject matter leaking in, we’ll count the frequencies of common, non-subject words only. These are sometimes known as function words.

You know what? Let’s turn it into a fun game!

The list below contains eight groups of words. Some of the groups are more common in human-written comments, and some of the groups are more common in robot-generated comments. For each row, mark it as either “human” or “robot” and see how many you get right!

  1. your of my other over since enough they be few towards
  2. which how this have about down could out much should
  3. must everything before away among the thousand past until their
  4. while every both one each none within per nothing once outside
  5. so its whether against through back would across up first several
  6. therefore yet when off still inside around after from at without
  7. more though his can although but except such second either
  8. we you who I will because some there these why along our

Done? Then you can check your answers. The first and last two groups (1–2 and 7–8) contain words often used in human-written comments. The middle four groups (3–6) contain words often used in robot-generated comments.

What we find is that robots generate comments that contain more prepositions and directions (through, up, among, within, around, after, at, etc.), whereas humans write comments with more pronouns, qualifications, and story-weaving words (we, you, because, although, except, should, either, some, etc.)

Part-of-speech frequencies (54 %)

The next step up in abstraction from words might be part-of-speech (pos) tags. A pos tagger replaces words with their grammatical category, which can uncover sentence structures at a higher level than what’s visible through words alone.

I have to admit I’m not very skilled with grammatical categories, so I can’t comment intelligently on this, but some markers of human text include

  • personal pronouns (“we”, “us”)
  • existentials (“there”, “these”, when they point to something)
  • modals (“should”, “can”)
  • questioning pronouns (“who”, “what”)
  • symbols (abbreviations like “geo” and typographical symbols like the at sign)
  • superlatives (“latest”, “most”)

In contrast, robot generated comments contain more

  • interjections (“so”, “e.g.”, “yes”)
  • 3rd person singular present verbs (“affects”, “is”, “has”)
  • past participle verbs (“created”, “copied”, “associated”)
  • predeterminers (“all”, “half”, “less than”)
  • particles (“up”, “back”, “over”, when they modify the words that come before, as in “start over”)

This hints that maybe the apparent high number of prepositions and directions in robot-generated comments are actually serving the function of particles, rather than prepositions and directions.

That said, this is the weakest discriminator so far, at only 54 % accuracy – barely better than chance.

Character bigrams (66 %)

Instead of looking at the frequencies of single characters, we can look at the frequencies of combinations of two characters next to each other. This isn’t at a higher level of abstraction, but it does tell us more about how robots and humans differ in how they shape their words and how they use punctuation.

The major discovery from this lens is that robots much more frequently end their comments with a full stop. I have advocated for humans to do so for a long time3 In part because it lets a reader know if a comment is accidentally truncated, but also because it forces better sentence construction on the author of the comment, which usually improves the quality of the comment. but they haven’t caught up, so comments ending in full stops are evidence that a robot generated the comment.

Robots also have some funny quirks around word choices that start to appear at this level of abstraction. These characters are more common in the beginnings and endings of words generated by robots than written by humans:

  • Words that begin with “re-” (such as “report”, “recover”, “relocate”).
  • Words that begin with “co-” (such as “coverage”, “config”, “copies”).
  • Words that begin with “st-” (such as “store”, “stub”, “start”).
  • Words that end with “-er” (such as “container”, “user”, “writer”).
  • Words that end with “-al” (such as “terminal”, “individual”, “global”).
  • Maybe also words that end with “-k” and “-p”.

Some of this I’ve noticed myself (the affinity for “re-” words), and I’m willing to believe it could maybe be a weak signal, at best. I speculate that robots get rlhfd into using punchier words, and those prefixes and suffixes are punchier, maybe.

Character trigrams (68 %)

Once we get into character trigrams, we have to be careful again, because character trigrams are long enough that they start to capture subject differences through parts of words. To reduce that risk, I extracted a list of a few hundred of the most discriminative full words, and added a preprocessing step that removes those that seemed like they leaked subject matter before passing the comments through the trigram analysis.

Looking at the difference through the character trigram lens tells us very little new. This is where we learn that robots are trained to use the word “load” (through the “oad” trigram), as well as words that sound like “surround”, “counter”, and “account” (through the “oun” trigram.)

The reason a model based on character trigrams gets such a high accuracy despite not uncovering anything new is that it captures also the results from virtually all earlier layers. A trigram model gets many of the same signals we discovered through character frequencies, word frequencies, and bigram frequencies. Given a small enough vocabulary with a large enough training data set, it can even detect signals in pos tag frequencies.

Part-of-speech bigrams (63 %)

If we could learn from bigrams of characters, maybe we can look also at pos tag bigrams. That’s a good idea, because this is where sentence structures start to show through – and why pos tag bigrams give much higher accuracy than plain pos tag frequencies.4 Although it should be noted that pos tagging is expensive, and still not meaningfully better than mere character frequencies.

Common pos bigrams are weighted toward personal pronouns and verbs:

  • Personal pronoun + verb or modal, such as “we are”, “they should”, “we may”, “we can”, “we want”, “we deprecate”, “us have”.
  • Determiner + verb, such as “this guarantees”, “this is”, “this gives”.
  • Personal verb + to, such as “want to”, “have to”, “move to”.
  • Existential there + verb, such as “there is”, “these are”.
  • Modal + verb, such as “should be”, “can cause”, “will add”.

Robot-generated comments are more abstract through adjectives, and more complex through conjunctions:

  • Determiner, possessive, or noun + adjective, such as “an individual X”, “a single X”, “the only X”, “its border-right X”, “its vertical X”, “its already-persisted X”.
  • Conjuction + determiner, such as “and a”, “and the”, “or both”.

Although we may recognise some of these patterns from personal experience, pos bigrams are not a very powerful model alone.

Part-of-speech trigrams (65  % accuracy)

We can take it one step further and look at consecutive triples of pos tags to capture even more sentence structures. Accuracy is not much higher than for pos bigrams, but we might recognise many of the human and robot constructions as such.

Here are some examples of human-style writing, which features a lot of verbs and personal pronouns:

  • “we may want”, “we can attach”, “they should upload”
  • “want to store”, “have to solve”, “want to remove”
  • “there is no”, “there is a”, “these are a”
  • “since we do”, “after we apply”, “so we check”
  • “we are looking”, “I’m guessing”, “we are determining”
  • “we check the”, “we do no”, “we have a”
  • “will trigger the”, “can have the”, “should cause no”
  • “sends data for”, “tells users that”, “causes errors in”

In contrast, robot-generated comments are adjective-laden:

  • “only difference is”, “first tab is”, “inner query aggregates”
  • “col’s border-right”, “highlight’s vertical”, “layer’s already-persisted”
  • “an individual checkbox”, “a single line”, “the only difference”
  • “the original uploaded”, “a functioning green”, “the correct quick”
  • “this flag is”, “the layout covers”, “this version does”
  • “surround option affects”, “reporting service prints”, “state parameter contains”
  • “thread reference autocomplete”, “used compression ratio”, “compared year title”

Looking at these examples, it would seem like robots use more big words than humans too, but I haven’t tested that.

All features at once (75 %)

If we jam all features at once into the model to try to get them to cancel out their redundancies, here is what remains, in order of most predictive isolated feature to least:

  1. Robots use em dashes more than humans.
  2. Robots use the connective interjection “so” more than humans.
  3. Robots end comments with full stops more than humans.
  4. Robots use semicolons more than humans.
  5. Robots use parentheses more than humans.
  6. Robots use the possessive form “its” more than humans.
  7. Robots use adjectives more than humans.
  8. Robots use the word “whether” more than humans.

After these top eight, other predictors start to get fuzzy and difficult to interpret in isolation. I suspect much of the power of the classifier does not come from individually strong signals (like the em dash and other typographic quirks), but from combinations of other signals that collectively paint a coherent picture. But those combinations might be different for different documents, and they won’t show up as individual predictors in a list like this.

It would be a cool experiment to have the classifier output the relevant feature combinations when the user hovers over parts of an input to get a better sense of how the classifier sees text, but I haven’t built that.

Steps to build a classifier

The first step to building any classifier is producing labeled data. In this case, that would be a large set of examples of both robot-generated comments and human-generated comments, where each example is annotated with what category it belongs to. I don’t have that. If you have that, I would love to get my hands on it! I settled for a proxy: the Internet gives us access to repositories of code that didn’t get any robot-generated comments a few years ago, but whose comments added recently are mainly generated by robots. We can pick an arbitrary date – let’s say October 2025 – and train our model to classify comments as written before or after that cutoff date. This will accidentally make it classify comments as written by humans or robots, too, although the overlap won’t be perfect.

Then we can write a script that goes through the git log and creates a large file with code comments and their date of addition. The script appends special start-of-comment and end-of-comment tokens to give the classifier an opportunity to discriminate on how comments end and begin. Whitespace is preserved, so any style choices surrounding whitespace make it into the model, too.

Another script reads a comment and produces feature vectors based on the lenses we’ve already discussed (character n-grams, pos tag n-grams, frequency of common words). These are smushed together and thrown into a huge logistic regression model.5 I gather the typical choice is a support vector machine, but I like the interpretability of the log-odds that come out of the logistic regression.

The features are based on relative frequencies rather than absolute counts, because I didn’t want the length of the comment to be used as a signal (longer comments tend to be robot-generated, and also produce higher absolute counts of features). The drawback of this is that when the logistic regression model makes a prediction, all the values involved are tiny percentages, so the prediction ends up underconfident for long inputs, where we would think there’s a lot of evidence. Thus, we can add a separate step that scales up the confidence by an appropriate fraction of the square root of input length, the fraction being calibrated during training.

There are good reasons I don’t want to share any specific code or data from this iteration of the classifier. The big one is that repositories containing revealing personal details in them6 E.g. one of the repositories that contain examples of human comments is the text adventure I’m developing for my children. It needs to be private because it involves a lot of actual incidents and information on our family and relatives. have been used as training data. I have some ideas on a future iteration that could be made open, which would also get two other flies pregnant7 This is a translation of a Swedish mixed metaphor. Two birds with one stone in Swedish is “two flies in one swat”, but the Swedish for “one swat” (“en smäll”) sounds similar to a slang term for “pregnant” (“på smällen”). So you can say “two flies pregnant” and people will hear something’s off but won’t be able to tell if it’s an honest mistake or a joke. When I first met my wife, she loved making that joke. She has since outgrown it; I have not.: it would allow better labeling, and more model transfer.

Out-of-sample testing

This model was trained specifically on source code comments from a small number of repositories. I suspect the robot comments mainly come from Anthropic models. It might be interesting to learn how well such a model generalises.

One natural question would be, “Can it detect non-source-code-comment texts generated by Anthropic models?” and the answer is a tentative “yes”, although I haven’t spent much time evaluating that. There’s also “Can it detect text by Anthropic models instructed to use a different style?” and the answer is “I don’t know”, because I haven’t tested that at all.

The third question is “can it detect source code comments written by other llm models?” and the answer is a resounding “no”. The model, as trained, is very good at detecting the Anthropic house style, which means when fed source code comments generated by other llm models, it often8 Eight times out of nine. classifies them as “not Anthropic”, which is the correct answer, but in my case is easy to misinterpret as “human-written”, which is the wrong answer.

This means there is significantly less transfer than I had at first thought! I had imagined most llm models have the same annoying style, but it seems they have detectably different annoying styles. It would be a fun project to expand the scope of the analysis to cover also other models, to see which things they have in common and which are different, but I’m not doing that now. Sorry!