Legitimizing Blockchain

Why is advertising blockchain something we should try to stop, rather than ignore?

Yesterday, 1Password made the following announcement:

I am very unhappy about this.

As of this writing, the replies to this announcement are, by my count, roughly 95% paying customers who are furious with them for doing this, 3% scammers who are jubilant that this is popularizing their scamming tool of choice, and about 2% blockchain-enthusiasts expressing confusion as to why everyone is so mad.

Scanning through that 2%’s twitter bios and timelines, I could see content other than memes and shilling, so it seemed at least plausible to me that these people are scam victims who haven’t gotten to the blow-off yet, and their confusion is genuine. Given that “why is everyone so mad” is a much less intense reaction than fury or jubilation, I assume that many others read through some of the vitriol and had this reaction, but then didn’t post anything themselves.

This post is for two audiences: that 2%, genuinely wondering what the big deal is, and also those who have a vague feeling that cryptocurrency is bad, but don’t see the point of making much of a fuss about it.

This is why we should make a fuss about it.


The objection most often raised in the comments went something like this:

This is just a feature that you don’t like; if it’s not for you, just don’t use it. Why yell at 1Password just for making a feature that makes someone else happy?

To begin with, the actual technical feature appears to be something related to auto-filling in browser-extension UI, which is fine. I don’t object to the feature. I don’t even object to features which explicitly help people store cryptocurrency more securely, as a harm reduction measure.

Also, to get this out of the way now: cryptocurrency is a scam. I’m not going to argue the case for that here. Others have made the argument far more exhaustively, and you can read literally hundreds of pages and watch hours of video explaining why by clicking here.

The issue is with the co-marketing effort: the fact that 1Password is using their well-respected brand to help advertise and legitimize scam-facilitation technology like Solana and Phantom.

Even if we were to accept all this, it’s a scam, 1Password is marketing it, etc, my hypothetical blockchain-curious interlocutor here might further object:

What’s the big deal about legitimizing these things, even if they are fraud? Surely you can just not get defrauded, and ignore the marketing?

That’s true, but it also misses the point: legitimizing and promoting these things does various kinds of harm.

More broadly, although I’m writing about 1Password’s specific announcement here, and a small amount of the reasoning will be specific to password management tools, most of the concerns I’ll describe are fairly general to any company promoting or co-marketing with cryptocurrency, and thus hopefully this post will serve for future instances where we should tell some other company to stop supporting blockchains as well.

So with all that out of the way, here are some of the harms that one might be concerned about, from the least selfish concern to the most.


Concern #1: the well-being of others

I don’t know how to explain to you that you should care about other people, but if you do care about other people, this could hurt them.

First and foremost, the entire scam of cryptocurrency rests upon making people believe that the assets are worth something. Most people are not steeped in the technical minutiae of blockchains, and tend to trust things based on institutional reputation. 1Password has a strong brand, related to information security, and they’re saying that cryptocurrencies are good, so it’s likely to convince a nonzero number of people to put their money into this technology that has enormous non-obvious risks. They could easily lose everything.

Advertising 1Password in this way additionally encourages users to maintain custody of their own blockchain assets on their own devices. Doing so with 1Password is considerably less risky than it might be otherwise, so if this were to only reach people who were already planning to store their wallets on their own computers, then great.

However, this might encourage users who had not previously thought to look at cryptocurrency at all to do so, and if they found it via 1Password they might start using 1Password to store their first few secrets. Storing them in this way, although less risky, is still unreasonably risky, given the lack of any kind of safety mechanisms on blockchain-backed transactions. Even if they’re savvy enough not to get scammed, nobody is savvy enough not to get hacked, particularly by sophisticated technical attacks which are worth leveraging against high-value targets like people with expensive crypto wallets on their computers.

To be clear, crypto exchanges are, on average, extremely bad at the job of not getting their users money stolen, but individual users are likely to be even worse at that job.

Concern #2: economic damage

If you don’t care about other people much, but you still care about living in a functioning society, then the promotion of blockchain based financial instruments is a huge destabilization risk. As Dan Olson explains in the devastating video essay / documentary Line Goes Up, blockchain-based financial instruments share a lot of extremely concerning properties that made mortgage-backed securities and collateralized debt obligations so financially toxic in the 2008 crash. Large-scale adoption of these things could lead to a similar crisis, or even worse, a global deflationary spiral in the style of the one that caused the great depression, setting off the kind of economic damage that could result in mass famine and mass death.

Of course, any individual company or celebrity advertising crypto is not going to trigger an immediate economic collapse. Each of these is a snowflake in an avalanche. I have no illusions that convincing just 1Password to stop this is going to turn the tide of the entire blockchain catastrophe that is unfolding all around us, or indeed that my one little post here is going to make the decisive difference between, 1Password stopping vs. not.

But that’s exactly why I’m trying to persuade you, dear reader, that this is a big deal and we should all try to work together to stop it.

Concern #3: environmental damage

While this specific blockchain is “greener” than others, but given the huge proportion of cryptocurrency generally that is backed by electrical waste, and the cultural and technical incentives that make trading one blockchain asset for another more common than cashing out to dollars, it’s still a legitimate concern that promoting blockchain in general will promote environmental destruction indirectly.

Furthermore, the way that Solana is less energy-intensive than other blockchains is by using proof-of-stake, so there’s a sliding scale here between economic and environmental damage, given that proof-of-stake is designed to accelerate wealth accumulation among non-productive participants, and thereby encourages hoarding. So the reduction in environmental damage just makes the previous point even worse.

Concern #4: increased targeting risk

Even if you’re a full blown sociopath with no concern for others and an iron-clad confidence that you can navigate the collapse of the financial system without any harm to you personally, there is still a pretty big negative here: increased risk from threat actors. Even if you like and use blockchain, and want to use this feature, this risk still affects you.

If 1Password happened to have some features that blockchain nerds could use to store their secrets, then attackers might have some interest in breaking in to 1Password, and could possibly work on tools to do so. That’s the risk of existing on the Internet at all. But if 1Password loudly advertises, repeatedly, that they are will be integrating with a variety of cryptocurrency providers, then this will let attackers know that 1Password is the preferred cryptocurrency storage mechanism.

This further means that attackers will start trying to figure out ways to target 1Password users, on the assumption that we’re more likely to have crypto assets lying around on our filesystems; not only developing tools to break in to 1Password but developing tools to fingerprint users who have the extension installed, who have accounts on the service, whose emails show up on the forum, etc.

Now, of course, 1Password users keep plenty of high-value information inside 1Password already; that’s the whole point. But cryptocurrency is special because of the irreversible nature of transactions, and the immediacy of the benefit to cybercriminals specifically.

If you steal all of someone’s bank passwords, you could potentially get a bunch of their money, but it is expensive and risky for the criminals. The transactions can be traced directly to actual human account holders immediately; anti-money-laundering regulations mean that this can usually be accomplished even across international borders. Transfers can be reversed.

This discrepancy between real money and cryptocurrency is exactly why ransomware was created by cryptocurrency. It makes cryptocurrency attractive specifically to the kinds of people who have expertise and resources to mount wide-spectrum digital attacks against whole populations.

Of course, if they develop tools to fingerprint and hack 1Password users, but they don’t luck out and find easy-to-steal crypto on your computer, they might as well try to steal other things of value, like your identity, credit information, and so on. These are higher-risk, but now that they’ve built all that infrastructure and hacked all these machines, there’s a big sunk cost that makes it more worthwhile.

Please Stop

I really hope that 1Password abandons this destructive scheme. Even if they fully walk this back, I will still find it much harder to recommend their product in the future; there will need to be some active effort to repair trust with their user community. If I’ve convinced you of the problems here, please let them know as a reply to the tweet, the email linked from their blog post, their community forum, or the Reddit post of the announcement, so that they can get a clear signal that this is unacceptable.

A Better Pygame Mainloop

Fix your mainloop for smoother gameplay that takes less battery power.

This post recommends calling pygame.display.flip from a thread, which I tested extensively on mac, windows, and linux before posting, but after some feedback from readers, I realize that this strategy is not in fact cross-platform; specifically, the nvidia drivers on linux appear to either crash or display a black window if you try to do this. The SDL FAQ does say that you can’t call “video functions” from multiple threads, and flip does do that under the hood. I do plan to update this post again, either with a method to make it safe, or a method to use slightly more complex timing heuristics to accomplish the same thing. In the meanwhile, please be aware that this may cause portability problems for your code.

I’ve written about this before, but in that context I was writing mainly about frame-rate independence, and only gave a brief mention of vertical sync; the title also mentioned Twisted, and upon re-reading it I realized that many folks who might get a lot of use out of its technique would not have bothered to read it, just because I made it sound like an aside in the context of an animation technique in a game that already wanted to use Twisted for some reason, rather than a comprehensive best practice. Now that Pygame 2.0 is out, though, and the vsync=1 flag is more reliably available to everyone, I thought it would be worth revisiting.


Per the many tutorials out there, including the official one, most Pygame mainloops look like this:

1
2
3
4
5
6
7
8
pygame.display.set_mode((320, 240))

while 1:
    for event in pygame.event.get():
        handleEvent(event)
    for drawable in myDrawables:
        drawable.draw()
    pygame.display.flip()

Obviously that works okay, or folks wouldn’t do it, but it can give an impression of a certain lack of polish for most beginner Pygame games.

The thing that’s always bothered me personally about this idiom is: where does the networking go? After spending many years trying to popularize event loops in Python, I’m sad to see people implementing loops over and over again that have no way to get networking, or threads, or timers scheduled in a standard way so that libraries could be written without the application needing to manually call them every frame.

But, who cares how I feel about it? Lots of games don’t have networking1. There are more general problems with it. Specifically, it is likely to:

  1. waste power, and
  2. look bad.

Wasting Power

Why should anyone care about power when they’re making a video game? Aren’t games supposed to just gobble up CPUs and GPUs for breakfast, burning up as much power as they need for the most gamer experience possible?

Chances are, if you’re making a game that you expect anyone that you don’t personally know to play, they’re going to be playing it on a laptop2. Pygame might have a reputation for being “slow”, but for a simple 2D game with only a few sprites, Python can easily render several thousand frames per second. Even the fastest display in the world can only refresh at 360Hz3. That’s less than one thousand frames per second. The average laptop display is going to be more like 60Hz, or — if you’re lucky — maybe 120. By rendering thousands of frames that the user never even sees, you warm up their CPU uncomfortably4, and you waste 10x (or more) of their battery doing useless work.

At some point your game might have enough stuff going on that it will run the CPU at full tilt, and if it does, that’s probably fine; at least then you’ll be using up that heat and battery life in order to make their computer do something useful. But even if it is, it’s probably not doing that all of the time, and battery is definitely a use-over-time sort of problem.

Looking Bad

If you’re rendering directly to the screen without regard for vsync, your players are going to experience Screen Tearing, where the screen is in the middle of updating while you’re in the middle of drawing to it. This looks especially bad if your game is panning over a background, which is a very likely scenario for the usual genre of 2D Pygame game.

How to fix it?

Pygame lets you turn on VSync, and in Pygame 2, you can do this simply by passing the pygame.SCALED flag and the vsync=1 argument to set_mode().

Now your game will have silky smooth animations and scrolling5! Solved!

But... if the fix is so simple, why doesn’t everybody — including, notably, the official documentation — recommend doing this?

The solution creates another problem: pygame.display.flip may now block until the next display refresh, which may be many milliseconds.

Even worse: note the word “may”. Unfortunately, behavior of vsync is quite inconsistent between platforms and drivers, so for a properly cross-platform game it may be necessary to allow the user to select a frame rate and wait on an asyncio.sleep than running flip in a thread. Using the techniques from the answers to this stack overflow answer you can establish a reasonable heuristic for the refresh rate of the relevant display, but if adding those libraries and writing that code is too complex, “60” is probably a good enough value to start with, even if the user’s monitor can go a little faster. This might save a little power even in the case where you can rely on flip to tell you when the monitor is actually ready again; if your game can only reliably render 60FPS anyway because there’s too much Python game logic going on to consistently go faster, it’s better to achieve a consistent but lower framerate than to be faster but inconsistent.

The potential for blocking needs to be dealt with though, and it has several knock-on effects.

For one thing, it makes my “where do you put the networking” problem even worse: most networking frameworks expect to be able to send more than one packet every 16 milliseconds.

More pressingly for most Pygame users, however, it creates a minor performance headache. You now spend a bunch of time blocked in the now-blocking flip call, wasting precious milliseconds that you could be using to do stuff unrelated to drawing, like handling user input, updating animations, running AI, and so on.

The problem is that your Pygame mainloop has 3 jobs:

  1. drawing
  2. game logic (AI and so on)
  3. input handling

What you want to do to ensure the smoothest possible frame rate is to draw everything as fast as you possibly can at the beginning of the frame and then call flip immediately to be sure that the graphics have been delivered to the screen and they don’t have to wait until the next screen-refresh. However, this is at odds with the need to get as much done as possible before you call flip and possibly block for 1/60th of a second.

So either you put off calling flip, potentially risking a dropped frame if your AI is a little slow, or you call flip too eagerly and waste a bunch of time waiting around for the display to refresh. This is especially true of things like animations, which you can’t update before drawing, because you have to draw this frame before you worry about the next one, but waiting until after flip wastes valuable time; by the time you are starting your next frame draw, you possibly have other code which now needs to run, and you’re racing to get it done before that next flip call.

Now, if your Python game logic is actually saturating your CPU — which is not hard to do — you’ll drop frames no matter what. But there are a lot of marginal cases where you’ve mostly got enough CPU to do what you need to without dropping frames, and it can be a lot of overhead to constantly check the clock to see if you have enough frame budget left to do one more work item before the frame deadline - or, for that matter, to maintain a workable heuristic for exactly when that frame deadline will be.

The technique to avoid these problems is deceptively simple, and in fact it was covered with the deferToThread trick presented in my earlier post. But again, we’re not here to talk about Twisted. So let’s do this the no-additional-dependencies, stdlib-only way, with asyncio:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import asyncio
import time
from math import inf

from pygame.display import set_mode, flip
from pygame.constants import SCALED
from pygame.event import get

event_handler = ...
drawables = [...]

async def pygame_loop(framerate_limit=inf):
    loop = asyncio.get_event_loop()
    screen_surface = set_mode(size=(480, 255), flags=SCALED, vsync=1)
    next_frame_target = 0.0
    limit_frame_duration = (1.0 / framerate_limit)

    while True:

        if limit_frame_duration:
            # framerate limiter
            this_frame = time.time()
            delay = next_frame_target - this_frame
            if delay > 0:
                await asyncio.sleep(delay)
            next_frame_target = this_frame + limit_frame_duration

        for drawable in drawables:
            drawable.draw(screen_surface)
        events_to_handle = list(get())
        events_handled = loop.create_task(handle_events(events_to_handle))
        await loop.run_in_executor(None, flip)
        # don’t want to accidentally start drawing again until events are done
        await events_handled

async def handle_events(events_to_handle):
    # note that this must be an async def even if it doesn’t await
    for event in events_to_handle:
        event_handler.handle_event(event)

asyncio.run(pygame_loop(120))

Go Forth and Loop Better

At some point I will probably release my own wrapper library6 which does something similar to this, but I really wanted to present this as a technique rather than as some packaged-up code to use, since do-it-yourself mainloops, and keeping dependencies to a minimum, are such staples of Pygame community culture.

As you can see, this technique is only a few lines longer than the standard recipe for a Pygame main loop, but you now have access to a ton of additional functionality:

  • You can manage your framerate independence in both animations and game logic by just setting some timers and letting the frames update at the appropriate times; stop worrying about doing math on the clock by yourself!
  • Do you want to add networked multiplayer? No problem! Networking all happens inside the event loop, make whatever network requests you want, and never worry about blocking the game’s drawing on a network request!
  • Now your players’ laptops run cool while playing, and the graphics don’t have ugly tearing artifacts any more!

I really hope that this sees broader adoption so that the description “indie game made in Python” will no longer imply “runs hot and tears a lot when the screen is panning”. I’m also definitely curious to hear from readers, so please let me know if you end up using this technique to good effect!7


  1. And, honestly, a few fewer could stand to have it, given how much unnecessary always-online stuff there is in single-player experiences these days. But I digress. That’s why I’m in a footnote, this is a good place for digressing. ↩

  2. “Worldwide sales of laptops have eclipsed desktops for more than a decade. In 2019, desktop sales totaled 88.4 million units compared to 166 million laptops. That gap is expected to grow to 79 million versus 171 million by 2023.” ↩

  3. At least, Nvidia says that “the world’s fastest esports displays” are both 360Hz and also support G-Sync, and who am I to disagree? ↩

  4. They’re playing on a laptop, remember? So they’re literally uncomfortable. ↩

  5. Assuming you’ve made everything frame-rate independent, as mentioned in the aforementioned post. ↩

  6. because of course I will ↩

  7. And also, like, if there are horrible bugs in this code, so I can update it. It is super brief and abstract to show how general it is, but that also means it’s not really possible to test it as-is; my full-working-code examples are much longer and it’s definitely possible something got lost in translation. ↩

No More Stories

Journalists need to stop writing “stories” and start monitoring empirical consensus.

This is a bit of a rant, and it's about a topic that I’m not an expert on, but I do feel strongly about. So, despite the forceful language, please read this knowing that there’s still a fair amount of epistemic humility behind what I’m saying and I’m definitely open to updating my opinion if an expert on journalism or public policy were to have some compelling reason for the Chestertonian fence of the structure of journalistic institutions. Comments sections are the devil’s playground so I don’t have one, but feel free to reach out and if we have a fruitful discussion I’m happy to publish it here.

One of the things that COVID has taught me is that the concept of a “story” in the news media is a relic that needs to be completely re-thought. It is not suited to the challenges of media communication today.

Specifically, there are challenging and complex public-policy questions which require robust engagement from an informed electorate1. These questions are open-ended and their answers are unclear. What’s an appropriate strategy for public safety, for example? Should policing be part of it? I have my preferred snappy slogans in these areas but if we want to step away from propaganda for a moment and focus on governance, this is actually a really difficult question that hinges on a ton of difficult-to-source data.

For most of history, facts were scarce. It was the journalist’s job to find facts, to write them down, and to circulate them to as many people as possible, so that the public discourse could at least be fact-based; to have some basis in objective reality.

In the era of the Internet, though, we are drowning in facts. We don't just have facts, we have data. We don't just have data, we have metadata; we have databases and data warehouses and data lakes and all manner of data containers in between. These data do not coalesce into information on their own, however. They need to be collected, collated, synthesized, and interpreted.

Thus was born the concept of Data Journalism. No longer is it the function of the journalist simply to report the facts; in order for the discussion to be usefully grounded, they must also aggregate the facts, and present their aggregation in a way that can be comprehended.

Data journalism is definitely a step up, and there are many excellent data-journalism projects that have been done. But the problem with these projects is that they are often individual data-journalism stories that give a temporal snapshot of one journalist's interpretation of an issue. Just a tidy little pile of motivated reasoning with a few cherry-picked citations, and then we move on to the next story.

And that's when we even get data journalism. Most journalism is still just isolated stories, presented as prose. But this sort of story-after-story presentation in most publications provides a misleading picture of the world. Beyond even the sample bias of what kinds of stories get clicks and can move ad inventory, this sequential chain of disconnected facts is extremely prone to cherry-picking by bad-faith propagandists, and even much less malicious problems like recency bias and the availability heuristic.

Trying to develop a robust understanding of complex public policy issues by looking at individual news stories is like trying to map a continent's coastline by examining individual grains of sand one at a time.

What we need from journalism for the 21st century is a curated set of ongoing collections of consensus. What the best strategy is to combat COVID might change over time. Do mask mandates work? You can't possibly answer that question by scrounging around on pubmed by yourself, or worse yet reading a jumbled stream of op-ed thinkpieces in the New York Times and the Washington Post.

During COVID, some major press institutions started caving to the fairly desperate need for this sort of structure by setting up "trackers" for COVID vaccinations, case counts, and so on. But these trackers are still fit awkwardly within the "story" narrative. This one from the Washington post is a “story” from 2020, but has data from December 16th, 2021.

These trackers monitor only a few stats though, and don’t provide much in the way of meta-commentary on pressing questions: do masks work? Do lockdowns work? How much do we know about the efficacy of various ventilation improvements?

Each journalistic institution should maintain a “tracker” for every issue of public concern, and ideally they’d be in conversation with each other, constantly curating their list of sources in real time, updating conclusions as new data arrives, and recording an ongoing tally of what we can really be certain about and what is still a legitimate controversy.2


Declaratively

Insecure states should be unrepresentable.

This weekend a catastrophic bug in log4j2 was disclosed, leading to the potential for remote code execution on a huge number of unpatched endpoints.

In this specific case, it turns out there was not really any safe way to use the API. Initially it might appear that the issue was the treatment of an apparently fixed format string as a place to put variable user-specified data, but as it turns out it just recursively expands the log data forever, looking for code to execute. So perhaps the lesson here is nothing technical, just that we should remain ready to patch, or that we should pay the maintainers.

Still, it’s worth considering that injection vulnerabilities of this type exist pretty much everywhere, usually in places where the supposed defense against getting catastrophically RCE’d is to carefully remember that the string that you pass in isn’t that kind of string.

While not containing anything nearly so pernicious as a place to put a URL that lets you execute arbitrary attacker-controlled code, Python’s logging module does contain a fair amount of confusing indirection around its log message. Sometimes — if you’re passing a non-zero number of *args — the parts of the logging module will interpret msg as a format string; other times it will interpret it as a static string. This is in some sense a reasonable compromise; you can have format strings and defer formatting if you want, but also log.warning(f"hi, {attacker_controlled_data}") is fairly safe by default. It’s still a somewhat muddled and difficult to document situation.

Similarly, Twisted’s logging system does always treat its string argument as a format string, which is more consistent. However, it does let attackers put garbage into the log wherever the developer might not have understood the documentation.1

This is to say nothing of the elephant in the room here: SQL. Almost every SQL API takes a bunch of strings, and the ones that make you declare an object in advance (i.e. Java’s PreparedStatement) don’t mind at all if you create one at runtime.

In the interest of advancing the state of the art just a little here, I’d like to propose a pattern to encourage the idiomatic separation of user-entered data (i.e. attacker-controlled payloads) from pre-registration of static, sensitive data, whether it’s SQL queries, format strings, static HTML or something else. One where copying and pasting examples won’t instantly subvert the intended protection. What I suggest would look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# module scope
with sql_statements.declarations() as d:
    create_table = d.declare("create table foo (bar int, baz str)")
    save_foo = d.declare("insert into foo values (?, ?)")
    load_by_bar = d.declare("select * from foo where bar = :bar")

# later, inside a function
con = sqlite3.connect(":memory:")
cur = con.cursor()
create_table.run(cur)
save_foo.run(cur, 3, "hello")
save_foo.run(cur, 4, "goodbye")
print((list(load_by_bar.run(cur, bar=3))))

The idea here is that sql_statements.declarations() detects which module it’s in, and only lets you write those declarations once. Attempting to stick that inside your function and create some ad-hoc formatted string should immediately fail with a loud exception; copying this into the wrong part of your code just won’t work, so you won’t have a chance to create an injection vulnerability.

If this idea appeals to you, I’ve written an extremely basic prototype here on github and uploaded it to PyPI here.


  1. I’m not dropping a 0day on you, there’s not a clear vulnerability here; it only lets you draw data from explicitly-specified parameters into the log. If you use it wrong, you just might get an "Unable to format event" type error, which we'll go out of our way to not raise back to you as an exception. It just makes some ugly log messages. ↩

Unproblematize

I’ve got 999 problems.

The essence of software engineering is solving problems.

The first impression of this insight will almost certainly be that it seems like a good thing. If you have a problem, then solving it is great!

But software engineers are more likely to have mental health problems1 than those who perform mechanical labor, and I think our problem-oriented world-view has something to do with that.

So, how could solving problems be a problem?


As an example, let’s consider the idea of a bug tracker.

For many years, in the field of software, any system used to track work has been commonly referred to as a “bug tracker”. In recent years, the labels have become more euphemistic and general, and we might now call them “issue trackers”. We have Sapir-Whorfed2 our way into the default assumption that any work that might need performing is a degenerate case of a problem.

We can contrast this with other fields. Any industry will need to track work that must be done. For example, in doing some light research for this post, I discovered that the relevant term of art in construction3 is typically “Project Management” or “Task Management” software. “Projects” and “Tasks” are no less hard work, but the terms do have a different valence than “Bugs” and “Issues”.

I don’t think we can start to fix this ... problem ... by attempting to change the terminology. Firstly, the domain inherently lends itself to this sort of language, which is why it emerged in the first place.

Secondly, Atlassian has desperately been trying to get everybody to call their bug tracker a “software development tool” where you write “stories” for years, and nobody does. It’s an issue tracker where you file bugs, and that’s what everyone calls it and describes what they do with it. Even they have to protest, perhaps a bit too much, that it’s “way more than a bug and issue tracker”4.


This pervasive orientation towards “problems” as the atom of work does extend to any knowledge work, and thereby to any “productivity system”. Any to-do list is, at its core, a list of problems. You wouldn’t put an item on the list if you were happy with the way the world was. Therefore every unfinished item in any to-do list is a little pebble of worry.

As of this writing, I have almost 1000 unfinished tasks on my personal to-do list.

This is to say nothing of any tasks I have to perform at work, not to mention the implicit Śâ€Ž0 of additional unfinished tasks once one considers open source issue trackers for projects I work on.

It’s not really reasonable to opt out of this habit of problematizing everything. This monument to human folly that I’ve meticulously constructed out of the records of aspirations which exceed my capacity is, in fact, also an excellent prioritization tool. If you’re a good engineer, or even just good at making to-do lists, you’ll inevitably make huge lists of problems. On some level, this is what it means to set an intention to make the world — or at least your world — better.

On a different level though, this is how you set out to systematically give yourself anxiety, depression, or both. It’s clear from a wealth of neurological research that repeated experiences and thoughts change neural structures5. Thinking the same thought over and over literally re-wires your brain. Thinking the thought “here is another problem” over and over again forever is bound to cause some problems of its own.

The structure of to-do apps, bug trackers and the like is such that when an item is completed — when a problem is solved — it is subsequently removed from both physical view and our mind’s eye. What would be the point of simply lingering on a completed task? All the useful work is, after all, problems that haven’t been solved yet. Therefore the vast majority of our time is spent contemplating nothing but problems, prompting the continuous potentiation6 of neural pathways which lead to despair.


I don’t want to pretend that I have a cure for this self-inflicted ailment. I do, however, have a humble suggestion for one way to push back just a little bit against the relentless, unending tide of problems slowly eroding the shores of our souls: a positivity journal.

By “journal”, I do mean a private journal. Public expressions of positivity7 can help; indeed, some social and cultural support for expressing positivity is an important tool for maintaining a positive mind-set. However, it may not be the best starting point.

Unfortunately, any public expression becomes a discourse, and any discourse inevitably becomes a dialectic. Any expression of a view in public is seen by some as an invitation to express its opposite8. Therefore one either becomes invested in defending the boundaries of a positive community space — a psychically exhausting task in its own right — or one must constantly entertain the possibility that things are, in fact, bad, when one is trying to condition one’s brain to maintain the ability to recognize when things are actually good.

Thus my suggestion to write something for yourself, and only for yourself.

Personally, I use a template that I fill out every day, with four sections:

  • “Summary”. Summarize the day in one sentence that encapsulates its positive vibes. Honestly I put this in there because the Notes app (which is what I’m using to maintain this) shows a little summary of the contents of the note, and I was getting annoyed by just seeing “Proud:” as the sole content of that summary. But once I did so, I found that it helps to try to synthesize a positive narrative, as your brain may be constantly trying to assemble a negative one. It can help to write this last, even if it’s up at the top of your note, once you’ve already filled out some of the following sections.

  • “I’m proud of:”. First, focus on what you personally have achieved through your skill and hard work. This can be very difficult, if you are someone who has a habit of putting yourself down. Force yourself to acknowledge that you did something useful, even if you didn’t finish anything, you almost certainly made progress and that progress deserves celebration.

  • “I’m grateful to:”. Who are you grateful to? Why? What did they do for you? Once you’ve made the habit of allowing yourself to acknowledge your own accomplishments, it’s easy to see those; pay attention to the ways in which others support and help you. Thank them by name.

  • “I’m lucky because:”. Particularly in post-2020 hell-world it’s easy to feel like every random happenstance is an aggravating tragedy. But good things happen randomly all the time, and it’s easy to fail to notice them. Take a moment to notice things that went well for no good reason, because you’re definitely going to feel attacked by the universe when bad things happen for no good reason; and they will.

Although such a journal is private, it’s helpful to actually write out the answers, to focus on them, to force yourself to get really specific.

I hope this tool is useful to someone out there. It’s not going to solve any problems, but perhaps it will make the world seem just a little brighter.


  1. “Maintaining Mental health on Software Development Teams”, Lena Kozar and Vova Vovk, in InfoQ ↩

  2. Wikipedia page for “Linguistic Relativity” ↩

  3. “Construction Task and Project Tracking”, from Raptor Project Management Software ↩

  4. Jira Features List, Atlassian Software ↩

  5. “Culture Wires the Brain: A Cognitive Neuroscience Perspective”, Denise C. Park and Chih-Mao Huang, Perspect Psychol Sci. 2010 Jul 1; 5(4): 391–400. ↩

  6. Long-term potentiation and learning, J L Martinez Jr, B E Derrick ↩

  7. The #PositivePython hashtag on Twitter was a lovely experiment and despite my cautions here about public solutions to this problem, it’s generally pleasant to participate in. ↩

  8. As we well know. ↩