Do-nothing scripting: the key to gradual automation (2019)
164 comments
·February 7, 2025IanCal
0xfaded
My not-so-humble opinion is that this type of incremental encoding of process as scripts results in a system built around process rather than a system that eats process. I'm all for a catalog of scripts automating common tasks. The moment those scripts start getting called by production services you're creating a huge mountain of tech debt for whoever is unfortunate enough to unpick the web of spaghetti.
Further, because your human oriented process has now codified your system, changes to the system that's would be beneficial to a software system (i.e. dividing the system into components) are impossible. So incremental scale is achieved by stuffing more into the process, which then gets grafted onto the script monolith in a self protruding cycle.
IanCal
I'm confused as to what you're picturing.
> The moment those scripts start getting called by production services
Any of them with manual steps just can't, right?
> Further, because your human oriented process has now codified your system, changes to the system that's would be beneficial to a software system (i.e. dividing the system into components) are impossible.
Absolutely nothing stops you from changing these scripts - it's no different from having scripts in a package.json
The alternatives are full automation, which pushes your issues to the max, or zero automation which is an odd point to be in.
dragonwriter
> Any of them with manual steps just can't, right?
If you are literally using the kind of scripts used here, they can't be called by a production system though their use can be part of formalized desk procedures.
It is a fairly natural next step (and a not unheard of first step in automation, bypassing these) to instead build something like these scripts but into an actual workflow management system where it can be triggered by either a user or even automatically tied to another system event, can be assigned to an available worker, can have status and artifacts tracked, and can trigger downstream processes.
(Of course, once you have the process in such a system, where the human worker is essentially a component the system calls to complete defined steps—contrary to GPs criticism—what you have is equqivalent to a documented process with the notable different that it is much easier to refactor it to divide work among components, etc., just loke any other software system.)
szundi
No, the point of the article is that later these can be modified to actually run these commands - obviously to call these scripts. You can assume pretty confidently that when it seems to be a self contained good script, it is going to be called from services. Even if it is a bad investment to implement it, because programmers hate boring tasks. They would even choose to make a web of spaghettis for later generations than do boring stuff.
wavemode
How would a do-nothing script be called by production? The whole point is that they're manual.
You're basically arguing against the concept of SOPs. OP blog post is basically just a programmer's version of an SOP.
SOPs are great, until they aren't. So then you change them. This is nothing new or strange.
j45
Bash scripts have a lot less of these issues than scripting built in a build orchestration system.
They are more portable, more universal, fewer dependencies.
Whenever you start to install something just put the commands into a bash’s script as you go and run it at each step.
Want to break it apart? Break apart the script how you like and repeat.
Being able to hand someone a scene for Linux or MacOS is a great way to help others begin too.
hinkley
My previous approach was:
1) Capture the process as is
2) make the process pass a giggle test
3) make the process automatable
Concurrent with 3 and 4:
- use the process repeatedly and by as many people as possible
- adapt the process to any misuse seen in the wild
4) automate the process
Everybody will buy into steps 1&2, and most people will come around to 4, but doing step 3 usually will get you more people pushing for and understanding 4
The limitation of your approach I will say is that if you can automate the beginning of a process or the end it’s fine, but if you have two islands in the middle it doesn’t work better than a CLI tool with prompts.
IanCal
> The limitation of your approach I will say is that if you can automate the beginning of a process or the end it’s fine, but if you have two islands in the middle it doesn’t work better than a CLI tool with prompts
I think I may have poorly described my approach then. It's just the same thing, the choice of sheets or Jira or cli or a combo is only about meeting people where their current process is. A dev setup -> cli. A business process between trams -> Jira if that's the current flow. I'm not getting marketing to install my cli tool, and they'd not be the ones doing the manual steps anyway.
The point is just making a process into a series of clearly defined steps that can be manual and can be automated independently.
Your description of what happens alongside 3 and 4 is good, this was really valuable when I've done it. Just like releasing a MVP and finding out key problems with your imagined approach, the issues are usually not what you expect. You also have better test cases and examples.
Massive side benefit when I did this with data was that it also meant a process could be automated 95% of the time, manual 5% without changing the interface anyone worked with. All they saw was the ticket took longer really. That's great when you find absolutely wild stuff in some csv file.
catlifeonmars
What’s a giggle test?
hinkley
One of the fun things with SEI Capability Maturity Model is that you reach level 1 just by having your process written down at all, no matter how stupid it sounds.
But the giggle test is just something I borrowed from business, which is, “can I say this with a straight face?” Which eliminates a lot more censorious than you would think. Writing something down is one thing, making eye contact with someone while repeating it is a bit harder.
szundi
Good strategy if you have infinite money to fund it.
hinkley
Iteration doesn’t require infinite money and this sort of work more than pays for itself.
The last time I used it, which is probably eight times now, I freed a team up to attack more entrenched tech debt problems by making them no longer afraid of deploying to run trails instead of waiting to do feature toggles once a release cycle.
Every adult programmer should be able to think of four things they can work on when nobody is telling them what to do for the next little while, and Scrum produces a lot of those. This is just one of the things I do. And when you get close to a quantum of useful improvement you can usually get a story or two on the board.
Fixing your processes is an axe sharpening exercise. If you’d rather be dismissive enough to register a complaint then I feel sorry for the teams you work on.
itomato
Yes and for me this an another chance to use and share Jupyter notebooks.
Describe the thing. Offer boilerplate, examples, or parameterized executables.
The doc system that uses Notebooks de facto would be awesome. It’s too heavy to add as a layer to a Cloud SaaS like Confluence and offers too many opportunities for escalations as is..
dejj
I see the mountain of “Zen and the Art of Motorcycle Maintenance” coming into view.
The approach replaces Confluence instruction pages with semi-interactive walkthroughs (the conscious side).
The other side is test automation: it starts with a big ball of overfitted Xpaths and undocumented steps. Those get re-discovered every time the application changes (the subconscious side).
Hopefully, we reach the summit where we can execute quickly and still know how we got there and why.
dheera
I would prefer if the script did the things that already are a command.
It can confirm with the user: "Execute command (y/N)?"
Fuck cutting and pasting stuff from one terminal into another terminal. That's also focus-intensive.
Then prompt the user to do the manual stuff when it isn't yet a command:
"Look up the e-mail address for foo. Paste it here:"
"Put that shit in 1Password: Are you done (y/N)?"
IanCal
Then automate those points as and when it's worth doing. Nobody is saying you can't do that, in fact it's a very key selling point of a do nothing script - you can very easily upgrade it to a do something script.
The alternatives are "do everything scripts" which get lost in dealing with how to automatically manage 1password, or documentation which is just a do nothing script that doesn't lead you through the checklist.
patcon
I think the point is that it's a psychological hack to just get started. It's making a point, and the point is that it's even helpful without running the commands. Yeah, sure you can immediately make it run the command as soon as it exists -- the article is not implying that that's wrong
IanCal
You also get to a point that's functional but not optimal very quickly. Improvements can then be added incrementally by different people as and when it's worthwhile.
Xmd5a
>Fuck cutting and pasting stuff from one terminal into another terminal. This is the point of this approach. To offer incentives to turn into scripts maintenance procedures that come around often but for which there isn't enough budget.
I think jupyter notebooks are better suited to fill this role since you can gradually turn a 100% markdown wiki document into a script and offer one-click execution.
dolmen
This is an interesting approach.
However the task used as an example just shows how provisionning SSH keys was insecure at that company. In fact the user should generate his private key by himself and just provide the public key to the sysadmin to inject it in the system to grant access. AT NO POINT THE SYSDAMIN SHOULD HAVE A COPY OF THE PRIVATE KEY, even temporary. So the 1Password step shouldn't even be necessary.
By the way, I'm the author of github-keygen, a tool to automate the creation of SSH keys dedicated to GitHub access, and to setup SSH settings for that context.
chuckadams
Making unskilled users manage their keys themselves doesn't sound very secure either. That's the point of moving to a script.
JimBlackwood
> In fact the user should generate his private key by himself and just provide the public key to the sysadmin to inject it in the system to grant access.
I always found this such an annoying step to implement. We've switched to certificate based authentication on SSH - no more moving around public keys. Really simplified the whole process!
jodrellblank
Certificates have a private key and a public key, and you keep the private key secret and move the public key around, so ... how is that different, technically and organizationally?
What do you actually do, and how is it better?
jyounker
There is only one public key that installed on the servers. That key is the same everywhere. You have a self-serve system that generates short-lived certs to users.
push0ret
[dead]
jayd16
Is this really the bar? Doesn't IT own every part of the work machine with the private key anyhow? I realize passwords are different but private keys sit at rest in the machine.
hinkley
I finally tried this an about a year so after it was first posted.
Due to bugs in our toolchain we had a run book for hot fixes that was about twice as complicated as the normal release process.
I never got the credit it deserved but it went from people only using it for sev 1 issues and “last mile” work in epics that were winding up, say once every ten weeks, to using it on average once a week and a couple times 3 in one week. We were able to dig a lot deeper into tech debt because not every single thing had to be a feature toggle.
If you’re at a small company where cloning prod data into preprod is easy, you won’t see this sort of result. But I counted over 150 endpoints we talked to and I believe that averaged 3 per service. So that’s a lot of datasets, and some of them ingested in a Kafka-before-Kafka-existed sort of manner. We had only one guy who would even try to clone prod data, he only had the time (and really energy) to do it once or twice a year, and that was much slower than our customers and features morphed. So it was down to fiddling with the blue-green deployment process and jmeter to figure out if we were close and how to measure success/failure before we went live.
And in the end it was the fiddly error-prone build process that stymied people until I half-automated it.
Later on as we ramped up its use I hunted down all of the URLs for the manual steps and put them in a lookup table in the tool, and ended up exposing them for the normal validation process we did for release sign off as well. Which made that process a little faster and less stressful for the coordinator (that process was annoying enough that we round robined it through three separate teams to share the load)
notarobot123
> It lowers the activation energy for automating tasks
Does that mean the "do-nothing script" should eventually have some automated steps that do-something?
As a placeholder for future possible automation, this feels like the right balance between automation and efficiency. It allows you to make the first stab without investing too much and it leaves some low-hanging fruit for another time when the effort might be more obviously worthwhile. Thanks for sharing!
Jtsummers
Yes.
> Each step of the procedure is now encapsulated in a function, which makes it possible to replace the text in any given step with code that performs the action automatically.
remram
class Foo(object):
def run(self, context): ...
Objects with only a single method to run it are already built into Python, they are the functions. def foo(context): ...
porridgeraisin
But how can one survive without a daily dose of Utterly Brain Damaged Abstractions?
itherseed
The advantage of the original approach is that later, when you need it, you can just add more methods to a class that are private to them. In your approach, if you need a new function, you add it at the global level, which can totally be fine for one or two functions, but with any more you end up with a bunch of functions all at the same level that is no longer obvious the dependency between them.
remram
You can do that with a function too, make it a class with a __call__. If you need it, which you won't for this kind a script.
heisenzombie
If you want a function that's private to a function in Python you _could_ also do:
def foo(context):
def bar(x):
return x*2
baz = bar(context)
... This is particularly idiomatic for higher order functions, but I think it can be useful for other things if used with restraint.But if you have functions that only need to be called from one calling function then I why not just inline the code and eschew having a function at all. Long function bodies for the win!
notfish
Don’t do this, it makes it a huge pain to test bar().
When you write the initial code to figure out bar, just throw that code in a unit test so you can run it any time. Stop throwing tests away!
muixoozie
Lol Reminds me of Brain Will's https://www.youtube.com/watch?v=QM1iUe6IofM he has a series basically ranting about this. One of them he goes into examples simplifying needlessly abstract code.
qwertox
It's great, but it can't be interrupted.
It would also be nice if it would show you all the steps beforehand, and then check each item as you progress. Sometimes it's good to actually prepare from a broader perspective.
And it could log into a file as a summary.
So much that could be improved, which is why the simplest solution might be the best.
IanCal
You could do this with a nicer cli library, so you could show the checklist and running output of each stage, which would be nice regardless of what steps are automated.
My main issue with that is that a do nothing shell script is so easy to get started with that it's hard not to complete it, and your efforts may be better used automating one of the steps. You may get lost in a fun but not that productive quagmire of which TUI library and how to structure everything.
vagab0nd
> but it can't be interrupted
I'm glad this is mentioned! This is why instead of a Bash script, I use a Makefile. Each step is a rule with a *.done name, that generates a .done file once it's done. I can interrupt it any time, modify the script to fix something, and `make` to resume.
But writing that Makefile is a PITA. Is there a better solution?
mwdomino
I’ve just switched to using just https://github.com/casey/just for my makefiles that were really just a collection of small snippets and it’s worked wonderfully
pigeons
Just?
chikere232
When I do this I keep some persistent state so I can interrupt it, e.g. if the thing is a yearly task I run it like `./do-the-thing.sh 2025`, and make a 2025 dir where I keep state on how far I've gotten
So if you OK the first step, I can touch a 2025/first-step file. If the script crashes or is interrupted and rerun, it can check for that file and skip the first step
If something has changed so the automation doesn't work, it's nice to be able to crash out without losing state, fix the script and rerun.
I usually have the script tell me just the next manual step and then exit, because that frees the terminal up for me to do other things. I can use the command history to rerun the script easily
gertlex
Are these actual criticisms of the article?
And you leave us hanging: which solution is the "simplest solution"?
codetrotter
I think the simplest solution would be to just do what the article says and not do all that extra stuff. And I think that that’s what they meant at the end of that comment too.
qwertox
Yes, this was what I meant. Sorry to your parent commenter for not being explicit.
(Then again, I'd do this in Obsidian.)
david422
Yea, I've used similar scripts where if you accidentally put in the wrong email address - oops, now what. You gotta restart the whole script? The script lockin can become a pain point.
Jtsummers
Past discussions (lots of comments):
https://news.ycombinator.com/item?id=29083367 - 3 years ago (230 comments)
https://news.ycombinator.com/item?id=20495739 - 6 years ago (124 comments)
iancmceachern
I cannot overstate how big a fan i am of this approach.
I've successfully applied this approach to so many projects. My favorite example is a $30 million surgical robot that was failing labs because of the "human factor".
treetalker
Would you mind sharing more details from one or more of your examples, or a summary of your experiences and tips (either here or in a blog post)? I would be interested to read, and from the looks of this thread, others would be too! I'm in a different field (law practice) but I'd love to consider how I could apply this approach in my firm.
IanCal
Often these are setup scripts or similar for a codebase, so you might have a readme with instructions like
1. Update the submodules by running ... 2. Install volta 3. Run `volta ...` 4. Setup a user account 5. Sync the dev database by running `...`
Now you can have that as documentation, or you can set it up as a checklist that runs the user through it and tells them what to do at each stage. Then you can swap out asking the user to do something to just doing it (e.g. 1, 3 and 5 are easy and 2 & 4 are at least less so).
> I'm in a different field (law practice) but I'd love to consider how I could apply this approach in my firm
The starting point is to consider any checklist type steps you have. Forgive me if I make up an example that sounds daft in your field but perhaps you have something like
Finalising client.
1. Ensure invoices are filed 2. Check all invoices have been paid 3. Send email to client 4. Set status of account to CLOSED on lawversionofsalesforcemaybethatsjustsalesforce
You can have this as a list of instructions. You could then change it to a checklist a person goes through and says which they've done. Then you might spot that #2 actually could just hit an API and check they've been paid. And #4 could be done too. You didn't have to automate 1 and 3, but you started with a useful thing and then incrementally removed busywork.
The approach is both really useful and also not really that big of a deal. The first step is just moving a checklist to a small program that lists the checklist. You're not automating anything. But then it makes the job of automating something in the list far smaller.
advael
I love this approach. I already like doing this in systems above a certain complexity made with programming languages too. I think the functional programming community calls this "holes"?
Interface "not implemented" errors follow a similar logic but I honestly think the value of writing something trivial that gives a meaningless but valid output for each of a bunch of pieces that depend on each other goes a long way toward expediting the process of building those pieces. It makes it much more likely you can test and build one thing at a time and not need to write a bunch of separate parts before you can test any of them
Having this type-wise validity matter in scripting contexts is sometimes harder, as in the use case described in the article, as a lot of the effects of command lines are going to be "side effects" from a functional perspective, but it being sequential makes this a lot less impactful and the waiting prompts make it so you can still preserve order of tasks at the low cost of needing manual steps you'd be doing without the script anyway
Scaffolds are incomplete but they're still, fundamentally, useful
al_borland
While I like this in theory, I think it would have trouble in practice. If an ops team is doing the same task over and over, and they see the do-nothing script does nothing, they will quickly stop using it once they think they’ve memorized the steps, or if they think it’s faster (or more interesting) to do it manually.
I’ve written a lot of automation and documentation for ops teams and getting them to use it, and use it constantly, has always been an issue. Doc changes also needed announcements, as people quickly stop reading them once they know how to do something.
In a perfect world, I think the approach makes a lot of sense, and I might even use it for some personal stuff. In practice the world is rarely perfect, and I think I’d only employ this if 90% was automated, but there was still 1 step I couldn’t quite get… and even then, I could see some members on the ops team skipping the manual step and assuming the whole thing is automated and magic.
chikere232
Often some bits are automatable, or some manual steps are verifiable, and then suddenly it's a do something script
starkparker
Ideally this documentation would also document the expected output, both toward identifying when the process has gone off the rails when doing it manually and making the steps testable once automated.
Otherwise, it'd be trivially easy for an unfamiliar user (or the automated script) to ignore unclear errors or exit codes and march blindly to the next wait_for_enter().
PaulRobinson
1. Write the runbook
2. Automate the runbook
3. Delete the runbook
This is just a means to get #2 done iteratively. I've done variations for a while, its a powerful technique.
I'm a big fan of this approach.
In general it's another way of defining interfaces around processes. Those processes may be manually done or automated. But the interface can remain the same - this is quite powerful when it comes to automating steps.
It's the same as you'd do with another system really.
I've used this before with Google sheets being manually filled in -> automated with a script. And with Jira tickets being raised and then automatically picked up and processed. It lets you get started sooner, automated the most annoying parts and you never have to fully do it.
Side benefit of do nothing scripts is that they can remain up to date more often as they can be more likely to be actually used than reading the docs.