Others have pointed out that there are architectural steps you can take to minimize this pain, like keeping all CI operations isolated within scripts that can be run locally (and treating GitHub Actions features purely as progressive enhancements, e.g. only using `GITHUB_STEP_SUMMARY` if actually present).
Another thing that works pretty well to address the feedback loop pain is `workflow_dispatch` + `gh workflow run`: you still need to go through a push cycle, but `gh workflow run` lets you stay in development flow until you actually need to go look at the logs.
(One frustrating limitation with that is that `gh workflow run` doesn't actually spit out the URL of the workflow run it triggers. GitHub claims this is because it's an async dispatch, but I don't see how there can possibly be no context for GitHub to provide here, given that they clearly obtain it later in the web UI.)
The action does nothing other than trigger the hook.
Then my server catches the hook and can do whatever I want.
It has the massive benefit of solving the lock-in problem. Your workflow is generally very short so it is easy to move to an alternative CI if (for example) Github were to jack up their prices for self hosted runners...
That said, when using it in this way I personally love Github actions
Nix specifies dependencies declaritively, and more precisely, than Docker (does by default), so the resulting environment is reproducibly the same. It caches really well and doubles as a package manager.
Despite the initial learning curve, I now personally prefer Nix's declarative style to a Dockerfile
This may also leverage docker (compose) to build/run different services depending on the stage of action. Sometimes also creating "builder" containers that will have a mount point for src and output to build and output the project in different OSes, etc. Docker + QEMU allows for some nice cross-compile options.
The less I rely on Github Actions environment the happier I am... the main points of use are checkout, deno runtime, release please and uploading assets in a release.
It sucks that the process is less connected and slow, but ensuring as much as reasonable can run locally goes a very long way.
I do have plans to create a couple libraries in the near future so will have to work through the pain(s)... also wanting to publish to jsr for it.
Most of these are off the shelf, at least in some programming languages. It’s the integrations and the overmanagement where a lot of the weight is.
Lefthook helps a lot https://anttiharju.dev/a/1#pre-commit-hooks-are-useful
Thing is that people are not willing to invest in it due to bad experiences with various git hooks, but there are ways to have it be excellent
Those things are fundamentally remote and kind of annoying to debug, but GitHub could invest a lot more in reducing the frustration involved in getting a fast remote cycle set up.
Production runs should be immutable, but we should be able to get in to diagnose, edit, and retry. It'd lead to faster diagnosis, resolution, and fixing.
The logs and everything should be there for us.
And speaking of the logs situation, the GHA logs are really buggy sometimes. They don't load about half of the time I need them to.
2. Don't have logic in your workflows. Workflows should be dumb and simple (KISS) and they should call your scripts.
3. Having standalone scripts will allow you to develop/modify and test locally without having to get caught in a loop of hell.
4. Design your entire CI pipeline for easier debugging, put that print state in, echo out the version of whatever. You don't need it _now_, but your future self will thank you when you do it need it.
5. Consider using third party runners that have better debugging capabilities
I've been using it for most of my local and environment scripting since relatively early on.
> The same Python testing code runs on all three platforms.
I have no objections to Python being used for testing, I use it myself for the end to end tests in my projects. I just don't think Python as a build script/task runner is a good idea, see below where I got Claude to convert one of my open source projects for an example.
I think pwsh is worth exploring. It is cross-platform. It is post-Python and the Python mantra that "~~code~~ scripts are read more often than they are written". It provides a lot of nice tools out of the box. It's built in an "object-oriented" way, resembling Python and owing much to C#. When done well the "object-oriented" way provides a number of benefits over "dumb text pipes" that shells like bash were built on. It is easy to extend with C# and a few other languages, should you need to extend it.
I would consider not dismissing it off hand without trying it just because Microsoft built it and/or that it was for a while Windows-only.
Meanwhile, if you can stick to the very basics, you can do anything more involved inside a container, where you can be confident that you, your CI environment, and even your less tech-savvy coworkers can all be using the exact same dependencies and execution environment. It eliminates entire classes of build and testing errors.
I know I've mentioned it a few times in this thread, just a very happy user and have found it a really good option for a lot of usage. I'll mostly just use the Deno.* methods or jsr:std for most things at this point, but there's also npm:zx which can help depending on what you're doing.
It also is a decent option for e2e testing regardless of the project language used.
It works okay, better than a lot of other workflows I have seen. But it is a bit slow, a bit cumbersome(for langs like Go or Node.js that want to write to HOME) and I had some issues on my ARM Macbook about no ARM images etc.
I would recommend taking a look at Nix, it is what I switched to.
* It is faster. * Has access to more tools. * Works on ARM, X86 etc.
Python is a lot easier to write safely.
Me, typically I have found it to be a sign of over-engineering and found no benefits over just using shell script/task runner, as all it should be is plumbing that should be simple enough that a task runner can handle it.
> If it works it works, as long as you can run it locally, it'll be good enough,
Maybe when it is your own personal project "If it works it works" is fine. But when you come to corporate environment there starts to be issues of readability, maintainability, proprietary tooling, additional dependencies etc I have found when people start to over-engineer and use programming languages(like Python).
E.g.
> never_inline 30 minutes ago | parent | prev | next [–]
> Build a CLI in python or whatever which does the same thing as CI, every CI stage should just call its subcommands.
However,
> and sometimes it's an even better idea to keep it in the same language the rest of the project is
I'll agree. Depending on the project's language etc other options might make sense. But personally so far everytime I have come across something not using a task runner it has just been the wrong decision.
Yeah, tends to happen a lot when you hold strong opinions with strong conviction :) Not that it's wrong or anything, but it's highly subjective in the end.
Typically I see larger issues being created from "under-engineering" and just rushing with the first idea people can think of when they implement things, rather than "over-engineering" causing similarly sized future issues. But then I also know everyone's history is vastly different, my views are surely shaped by the specific issues I've witnessed (and sometimes contributed to :| ), than anything else.
Strong opinions, loosely held :)
> Typically I see larger issues being created from "under-engineering" and just rushing with the first idea people can think of when they implement things, rather than "over-engineering"
Funnily enough running with the first idea I think is creating a lot of the "over-engineering" I am seeing. Not stopping to consider other simpler solutions or even if the problem needs/is worth solving in the first place.
> Yeah, tends to happen a lot when you hold strong opinions with strong conviction :) Not that it's wrong or anything, but it's highly subjective in the end.
I quickly asked Claude to convert one of my open source repos using Make/Nix/Shell -> Python/Nix to see how it would look. It is actually one of the better Python as a task runners I have seen.
* https://github.com/DeveloperC286/clean_git_history/pull/431
While the Python version is not as bad as I have seen previously, I am still struggling to see why you'd want it over Make/Shell.
It introduces more dependencies(Python which I solved via Nix) but others haven't solved this problem and the Python script has dependencies(such as Click for the CLI).
It is less maintainable as it is more code, roughly x3 the amount of the Makefile.
To me the Python code is more verbose and not as simple compared to the Makefile's target so it is less readable as well.
UV scripts are great for this type of workflow
There are even scripts which will install uv in the same file effectively making it just equivalent to ./run-file.py and it would handle all the dependency management the python version management and everything included and would work everywhere
https://paulw.tokyo/standalone-python-script-with-uv/
Personally I end up just downloading uv and so not using the uv download script from this but if I am using something like github action which are more (ephemeral?) I'd just do this.
Something like this can start out simple and can scale much more than the limitations of bash which can be abundant at times
That being said, I still make some shell scripts because executing other applications is first class support in bash but not so much in python but after discovering this I might create some new scripts with python with automated uv because I end up installing uv on many devices anyway (because uv's really good for python)
I am interested in bun-shell as well but that feels way too much bloated and even not used by many so less (AI assistance at times?) and I haven't understood bun shell at the same time too and so bash is superior to it usually
So previously when I have seen Python used as a task runner I think they used UV to call it. Although I don't think they had as a complete solution as your here auto-installing UV etc.
Although the example you've linked is installing UV if missing, the version is not pinned, I also don't think it is handling missing Python which is not pinned even if installed locally. So you could get different versions on CI vs locally.
While yes you are removing some of the dependencies problems created via using Python over Make/Shell I don't think this completely solves it.
> Something like this can start out simple and can scale much more than the limitations of bash which can be abundant at times
I personally haven't witnessed anytime I would consider the scales to have tipped in favour of Python and I would be concerned if they ever do, as really the task runner etc should be plumbing, so it should be simple.
> That being said, I still make some shell scripts because executing other applications is first class support in bash but not so much in python but after discovering this I might create some new scripts with python with automated uv because I end up installing uv on many devices anyway (because uv's really good for python)
Using Python/UV to do anything more complex than my example PR above?
I copied this from their website (https://docs.astral.sh/uv/guides/scripts/#declaring-script-d...)
uv also respects Python version requirements: example.py
# /// script # requires-python = ">=3.12" # dependencies = [] # ///
# Use some syntax added in Python 3.12 type Point = tuple[float, float] print(Point)
> Using Python/UV to do anything more complex than my example PR above?
I can agree that this might be complex but that complexity has a trade off and of course nothing is shoe fits all but there are times when someone has to manage a complex CI environment and I looked at and there are some CI deterministic options too like invoke etc. and when you combine all of these, I feel like the workflow can definitely be interesting to say the least
Once again, I don't know what really ends up in github actions since I have never really used it properly, I am basing its critiques based on what I've read and what solutions (python came quite frequently) and something recently which I discovered (which was the blog)
Oh, and later the author suggests the script modify itself after running. What the fuck. Absolutely unacceptable way to deploy software.
Also if this still bothers you, nothing stops you from removing the first x lines of code and having it in another .py file if this feels obnoxious to you
> Oh, and later the author suggests the script modify itself after running. What the fuck. Absolutely unacceptable way to deploy software.
Regarding author suggest its removes itself its because it does still feel clutterish but there is virtually 0 overhead in using/having it still be if you are already using uv or want to use uv
Oh also, (I am not the Author) but I have played extensively with UV and I feel like the script can definitely be changed to install it locally rather than globally.
They themselves mention it as #overkill on their website but even then it is better than whatever github action is
Someday, someone is going to have a really big disaster that comes out of casual getting unauthenticated stuff from somebody else's computer.
The other problem with shell scripting on things like GHA is that it’s really easy to introduce security vulnerabilities by e.g forgetting to quote your variables and letting an uncontrolled input through.
There’s no middle ground between bash and python and a lot of functionality lives in that space.
Maybe I keep making the wrong assumption that everyone is using the same tools the same way and thats why my opinions seem very strong. But I wouldn't even think of trying to "parse a semantic version" in shell, I am treating the shell scripts and task runners as plumbing, I would be handing that of a dedicated tool to action.
I'm a huge fan of "train as you fight", whatever build tools you have locally should be what's used in CI.
If your CI can do things that you can't do locally: that is a problem.
Probably most of the times when this is an actual problem, is building across many platforms. I'm running Linux x86_64 locally, but some of my deliverables are for macOS and Windows and ARM, and while I could cross-compile for all of them on Linux (macOS was a bitch to get working though), it always felt better to compile on the hardware I'm targeting.
Sometimes there are Windows/macOS-specific failures, and if I couldn't just ssh in and correct/investigate, and instead had to "change > commit > push" in an endless loop, it's possible I'd quite literally would lose my mind.
https://github.com/quickemu-project/quickemu [ Quickly create and run optimised Windows, macOS and Linux virtual machines ]
My laptop is an M1 MacBook Air, and I do have an N100 I could use for Windows... I'd just assume use my fast desktop which even emulated is likely faster and not have to move seats.
Oh btw although there are many primitives which help transferring files between VM's and others by having sshfs etc., one of the things which I enjoyed doing in quickemu is using the beloved piping-server
https://github.com/nwtgck/piping-server Infinitely transfer between every device over pure HTTP with pipes or browsers
The speeds might be slow but I was using it to build simple shell scripts and you can self host it or deploy on cf workers too most likely which is really simple but I haven't done it
But for quick deployments/transfers of binaries/simple files, its great as well. Tauri is meant to be lightweight/produce small binaries so I suppose one can try it but there are other options as well
Piping Serrvers + quickemu felt like a cheatcode to me atleast for more ephemeral vm's based workflow but of course YMMV
Good luck with your project! I tried building a tauri app once for android just out of mere curiosity on linux and it was hell. I didn't know anything about android development but setting up the developer environment was really hard and I think I forgot everything I learnt from that but wish I had made notes or even video documenting the process
IME this is where all the issues lie. Our CI pipeline can push to a remote container registry, but we can't do this locally. CI uses wildly different caching strategies to local builds, which diverges. Breaking up builds into different steps means that you need to "stash" the output of stages somewhere. If all your CI does is `make test && make deploy` then sure, but when you grow beyond that (my current project takes 45 minutes with a _warm_ cache) you need to diverge, and that's where the problems start.
Completely agree.
> I'm a huge fan of "train as you fight", whatever build tools you have locally should be what's used in CI.
That is what I am doing, having my GitHub Actions just call the Make targets I am using locally.
> I mean, at some point you are bash calling some other language anyway.
Yes, shell scripts and or task runners(Make, Just, Task etc) are really just plumbing around calling other tools. Which is why it feels like a smell to me when you need something more.
Of course, if you use something else as a task runner, that works as well.
Using makefiles mixes execution contexts between the CI pipeline and the code within the repository (that ends up containing the logic for the build), instead of using - centrally stored - external workflows that contains all the business logic for the build steps (e.g., compiler options, docker build steps etc.).
For example, how can you attest in the CI that your code is tested if the workflow only contains "make test"? You need to double check at runtime what the makefile did, but the makefile might have been modified by that time, so you need to build a chain of trust etc. Instead, in a standardized workflow, you just need to establish the ground truth (e.g., tools are installed and are at this path), and the execution cannot be modified by in-repo resources.
It doesn't matter whether you run "make test" or "npm test whatever": you're trusting the code you've checked out to verify its own correctness. It can lie to you either way. You're either verifying changes or you're not.
The easiest and most accessible way to attest what has been done is to have all the logic of what needs to be done in a single context, a single place. A reusable workflow that is executed by hash in a trusted environment and will execute exactly those steps, for example. In this case, step A does x, and step B attests that x has been done, because the logic is immutably in a place that cannot be tampered with by whoever invokes that workflow.
In the case of the makefile, in most cases, the makefile (and therefore the steps to execute) will be in a file in the repository, I.e., under partial control of anybody who can commit and under full control of those who can merge. If I execute a CI and step A now says "make x", the semantic actually depends on what the makefile in the repo includes, so the contexts are mixed between the GHA workflow and the repository content. Any step of the workflow now can't attest directly that x happened, because the logic of x is not in its context.
Of course, you can do everything in the makefile, including the attestation steps, bringing them again in the same context, but that makes it so that once again the security relevant steps are in a potentially untrusted environment. My thinking specifically hints at the case of an organization with hundreds of repositories that need to be brought under control. Even more, what I am saying make sense if you want to use the objectively convenient GH attestation service (probably one of the only good feature they pushed in the last 5 years).
I don't think it changes much, aside from security theater. If changes are not properly reviewed, then all fancy titles will not help. If anything, using Make will allow for a less flaky CI experience, that doesn't break the next time the git hoster changes something about their CI language and doesn't suffer from YAMLitis.
What's the threat model Wilder is using exactly? Look, I'm ordinarily all for nuance and saying reasonable people can disagree when it comes to technical opinions, but here I can't see any merit whatsoever to the claim that orchestrating CI actions with Make is somehow a security risk when the implementations of these actions at some level live in the repo anyway.
Neither do most people, probably but it's kinda neat how they suggested fix for github actions' ploy to maintain vendor lock-in is to swap it with a language invented by that very same vendor.
It seems like if you
> 2. Don't have logic in your workflows. Workflows should be dumb and simple (KISS) and they should call your scripts.
then you’re basically working against or despite the CI tool, and at that point maybe someone should build a better or more suitable CI tool.
Not to mention, Deno can run TS directly and can reference repository/http modules directly without a separate install step, which is useful for shell scripting beyond what pwsh can do. ex: pulling a dbms client and interacting directly for testing, setup or configuration.
For the above reasons, I'll also use Deno for e2e testing over other languages that may be used for the actual project/library/app.
This isn’t only a problem with GitHub Actions though. I’ve run into it with every CI runner I’ve come across.
For my actions, the part that takes the longest to run is installing all the dependencies from scratch. I'd like to speed that up but I could never figure it out. All the options I could find for caching deps sounded so complicated.
You shouldn't. Besides caching that is.
> All the options I could find for caching deps sounded so complicated.
In reality, it's fairly simple, as long as you leverage content-hashing. First, take your lock file, compute the sha256sum. Then check if the cache has an artifact with that hash as the ID. If it's found, download and extract, those are your dependencies. If not, you run the installation of the dependencies, then archive the results, with the ID set to the hash.
It really isn't more to it. I'm sure there are helpers/sub-actions/whatever Microsoft calls it, for doing all of this with 1-3 lines or something.
If I can avoid it, no. Almost everything I can control is outside of the Microsoft ecosystem. But as a freelancer, I have to deal a bunch with GitHub and Microsoft anyways, so in many of those cases, yes.
Many times, I end up using https://github.com/actions/cache for the clients who already use Actions, and none of that runs in the local machines at all.
Typically I use a single Makefile/Justfile, that sometimes have most of the logic inside of it for running tests and what not, sometimes shell out to "proper" scripts.
But that's disconnected from the required "setup", so Make/Just doesn't actually download dependencies, that's outside of the responsibilities of whatever runs the test.
And also, with a lot of languages, it doesn't matter if you run an extra "npm install" over already existing node_modules/, it'll figure out what's missing/there already, so you could in theory still have "make test" do absolute everything locally, including installing dependencies (if you now wish this), and still do the whole "hash > find cache > extract > continue" thing before running "make test", and it'll skip the dependencies part if it's there already.
For caching you use GitHubs own cache action.
For things like installing deps, you can use GitHub Actions or several third party runners have their own caching capabilities that are more mature than what GHA offers.
https://docs.github.com/en/actions/how-tos/manage-runners/la...
What? Bash is the best scripting language available for CI flows.
If you don't like it, you can get bash to work on windows anyway.
Also, GitHub actions itself just breaks sometimes, which is super annoying. A couple of weeks ago, half of all the macOS runner images broke when using GitHub's caching system. Seems like that would have been caught on GitHub's side before that happened, but what do I know!
Still no idea what happened or how to ever fix/prevent it again.
This is probably why they invented a whole programming language and then neglected to build any debugging tools for it.
Of course, the platforms would rather have you not do that since it nullifies their vendor lock-in.
1. When the build fails, you can SSH into the machine and debug it from there.
2. You can super easily edit & run the manifest without having to push to a branch at all. That makes it super easy to even try a minimum reproducible example on the remote machine.
Other than that, self-hosting (with Github or preferrably Forgejo) makes it easy to debug on the machine, but then you have to self-host.
As I said, I really like the SourceHut CI.
Sounds strange to say as someone who has a product that is built around making GitHub Actions exponentially faster to close these feedback loops faster.
But I can honestly say it's only really possible because the overall system with GitHub Actions is so poor. We discover new bottlenecks in the runner and control plane weekly. Things that you'd think would be simple are either non-existent, don't work, or have terrible performance.
I'm convinced there are better ways of doing things, and we are actively building ideas in that realm. So if anybody wants to learn more or just have a therapy session about what could be better with GitHub Actions, my email is in my bio.
Note that I don't really use github actions much but have heard about its architecture
From my understanding, I feel like Github actions should just be a call to some bash or python file. Bash has its issues so I prefer python
I recommend people to take a look at https://paulw.tokyo/standalone-python-script-with-uv/ and please tell me if something like this might be perfect for python scripts in github actions as this script would automatically install uv, get all the dependencies of python and even the runtime I think and then execute the python code all while being very managable usually and it can run locally as well
The only Issue I feel like I might have with this is say why go something with this complex when bash exists or the performance concerns of installing uv but considering its github actions, I feel like the latter is ruled out.
Bash is good as well but bash has some severe limitations. and I feel like Python can be good case for something like this plus its ecosystem is a bit mature and you could even create web servers or have some logs be reported to your custom server or automate just basically everything
To me this script feels like the best of both worlds and something genuinely sane to build upon.
...and that's how it is supposed to be used. The YAML file is mainly there for defining the runtime environment (e.g. the operating system or docker image to run in), and to describe the job dependency tree. The actual actions to execute should be delegated to scripts outside the GH YAML.
For me what worked wonders was adopting Nix. Make sure you have a reproducible dev environment and wrap your commands in `nix-shell --run`, or even better `nix develop --command`, or even better your most of your CI tasks derivations that run with `nix build` or `nix flake check`.
Not only does this make it super easy to work with Github Actions, also with your colleagues or other contributors.
gg watch action
Finds the most recent or currently running action for the branch you have checked out. Among other things.
edit: Just a quick note, the `gg` and `gg tui` commands for me don't show any repos at all, the current context stuff all works perfectly though.
I like being able to run self-hosted runners, that is a very cool part of GitHub Actions/Workflow.
I appreciate all the other advice about limit my yamls to: 1) checkout, 2) call a script to do the entire task. I am already half-way there, just need to knuckle-down and do the work.
I was dismayed that parallel tasks aren't really a thing in the yaml, I wanted to fanout a bunch of parallel tasks and I found I couldn't do it. Now that I'm going to consolidate my build process into a single script I own, I can do the fanout myself.
How about writing a separate repo and testing it separately
Keywords: reusable workflow/actions
Maybe that has changed.
The most important advice is probably to put as much code as possible into locally runnable scripts written in a cross-platform scripting language (e.g. Python or Node.js) to avoid 'commit-push-ci-failure' roundtrips.
Only use the GH Actions YAML for defining the runtime environment and job dependency tree.
1. Here's your goal "...", fix it, jj squash and git push, run gh pr checks --watch --fail-fast pr-link
Are there other platforms allowing that? Genuinely interested.
Ah, that's like 90% of the way there, just need to enable so the SSH endpoint is created at the beginning, rather than the end, so you could for example watch memory usage and stuff while the "real" job is running in the same instance.
But great to hear they let you have access to the runner at all, only that fact makes it a lot better than most CI services out there, creds to SourceHut.
Maybe it is, I've never tried :-). I don't see a reason why not, probably it is.
Because you can extend and override jobs, you can create seams so that each piece of the pipeline is isolated and testable. This way there is very little that can go wrong in production that's the CI fault. And I think that's only possible because of the way that Gitlab models their jobs and stages.
Who here has been thinking about this problem? Have you come up with any interesting ideas? What's the state of the art in this space?
GHA was designed in ~2018. What would it look like if you designed it today, with all we know now?
We started from the ideal state of CI, and set out to build the platform that would support that.
For us this ideal state of CI boils down to 4 things:
- local-first (local execution should be a first-class citizen, with no exception)
- repeatable (the same inputs should yield the same output, with affordances for handling inevitable side effects in an explicit and pragmatic way)
- programmable. my workflows are software. I want all the convenience of a modern software development experience: types, IDE support, a rich system API , debugging tools, an ecosystem of reusable components, etc.
- observable. I want all the information in one place about everything that happened in my workflow, with good tooling to get the information I need quickly, and interop with the existing observability ecosystem (eg. open telemetry)
So Dagger is our best effort at a CI platform focused on those 4 things.
Sorry if this comes across as a sales pitch. When you're building solves a problem you're obsessed with, it's hard to discuss the problem without also mentioning the solution that seems the most obvious to you :)
Passive comment readers should be aware that ^shykes here cofounded Docker (my gratitude), so it's really worth a look.
Can anyone comment on the ergonomics of Dagger after using it for a while?
I was just looking at the docs earlier this week to consider a migration but got confused by the AI sections...
You're not the only one... At some point last year, we discovered that CI/CD workflows and so-called "AI agent workflows" have a lot in common, and Dagger can in theory be used as an execution engine for both. We attempted to explain this - "great for CI/CD and for Agents!". But the feedback was mostly negative - it came across as confusing and lacking focus. So, we are rolling back this messaging and refocusing on CI/CD again. If you haven't checked our docs in the latest 12 hours, it's worth checking again: you'll see clear signs of this refocusing (although we are not done).
In doubt, I recommend joining our public discord server (https://discord.com/invite/dagger-io) it is basically a support group for CI/CD nerds who believe that a better way is possible, and want to discuss it with like-minded people.
Thanks for the kind words!
- local CLI instead of git push to run
- graph-based task definitions with automatic distributed execution, instead of the job/step abstraction
- automatic content-based caching to skip unnecessary executions (happens a lot in CI pipelines)
- container-based runtime (instead of proprietary base images) without using docker directly (too slow)
There are a lot of other ways to improve the developer experience. Happy to chat with anybody interested, I'm dan@rwx.com
GitHub action is a totally broken piece of s !! I know about that broken loops cause I had to deal with it an incredible number of times.
I very often mention OneDev in my comments, and you know what ? Robin solved this issue 3 years ago : https://docs.onedev.io/tutorials/cicd/diagnose-with-web-term...
You can pause your action, connect through a web terminal, and debug/fix things live until it works. Then, you just patch your action easily.
And that’s just one of the many features that make OneDev superior to pretty much every other CI/CD product out there.
It's the default CI system on github and you get relatively free compute.
There was a time I wanted our GH actions to be more capable, but now I just want them to do as little as possible. I've got a Cloudflare worker receiving the GitHub webhooks firehose, storing metadata about each push and each run so I don't have to pass variables between workflows (which somehow is a horrible experience), and any long-running task that should run in parallel (like evaluations) happens on a Hetzner machine instead.
I'm very open to hear of nice alternatives that integrate well with GitHub, but are more fun to configure.
I've seen this over and over again over the years in projects where things like ant, maven, gradle, puppet, ansible, etc. Invariably somebody tries to do something really complicated/clever in a convoluted way. They'll add plugins, more plugins, all sorts of complex configuration, etc. Your script complexity explodes. And then it doesn't work and you spend hours/days trying to make it do the right thing and fighting a tool that just wasn't designed to do what you are doing well. The problem is using the wrong tool for the job. All these tools have the tendency to evolve into everything tools. And they just aren't good at everything. Just because it has some feature doesn't mean it's a good idea to use it.
The author actually calls this out. Just write a bash script and run that instead. If that gets too complicated pick something else more appropriate to the job. Python, whatever you like. The point here is to pick something that's easy for you to run, test, and debug locally. Obviously people have different preferences here. If you are shoe horning complex fork/join behavior, conditional logic, etc. into a Yaml / CI build, maybe simplify your build. Yaml just isn't suitable as a general purpose programming language.
Externalizing the complex stuff to some script also has the benefit of that stuff still working if you ever decide to switch CI provider. Maybe you want to move to Gitlab. Or somebody decides Jenkins wasn't so bad after all. The same scripts will probably be easy to adapt to those.
One of my current github actions basically starts a vm, runs a build.sh script, stops the vm. I don't need a custom runner for that. I get to pick the vm type. I can do whatever I need to in my build.sh. I have one project with an actual matrix build. But that's about the most complex thing I do with github actions. A lot of my build steps are just inline shell commands.
And obligatory AI comment here, if you are doing all this manually, having scripts that run locally also means you can put claude code/codex/whatever to work fixing them for you. I've been working on some ansible scripts with codex today. Works great. It produces better ansible scripts than me. These tools work better if they can test/run what they are working on.
> For the love of all that is holy, don’t let GitHub Actions
> manage your logic. Keep your scripts under your own damn
> control and just make the Actions call them!
I mean your problem was not `build.rs` here and Makefiles did not solve it, was your logic not already in `build.rs` which was called by Cargo via GitHub Actions?The problem was the environment setup? You couldn't get CUE on Linux ARM and I am assuming when you moved to Makefiles you removed the need for CUE or something? So really the solution was something like Nix or Mise to install the tooling, so you have the same tooling/version locally & on CI?
"GitHub actions bad" is a valid take - you should reduce your use to a minimum.
"My build failed because of GitHub actions couldn't install a dependency of my build" is a skill issue. Don't use GitHub actions to install a program your build depends on.
i just checked and in 2025 there was at least 2 outages a month every month https://x.com/swyx/status/2011463717683118449?s=20 . not quite 3 nines.
For the script getting run, there's one other thing. I build my containers locally, test the scripts thoroughly, and those scripts and container are what are then used in the build and deploy via Action. As the entire environment is the same, I haven't encountered many issues at all.
Like most of the glaring nonsense that costs people time when using msft, this is financially beneficial to msft in that each failed run counts against paid minutes. It's a racket from disgusting sleaze scum who literally hold meetings dedicated to increasing user pain because otherwise the bottom line will slip fractionally and no one in redmond has a single clue how to make money without ripping off the userbase.
Being able to run your entire "pipeline" locally with breakpoints is much more productive than whatever the hell goes on in GH Actions these days.
Setting up my github actions (or gitlab) checks in a way that can easily run locally can be a bit of extra work, but it's not difficult.
> For the love of all that is holy, don’t let GitHub Actions
> manage your logic. Keep your scripts under your own damn
> control and just make the Actions call them!
The pain is real. I think everyone that's ever used GitHub actions has come to this conclusion. An ideal action has 2 steps: (1) check out the code, (2) invoke a sane script that you can test locally.Honestly, I wonder if a better workflow definition would just have a single input: a single command to run. Remove the temptation to actually put logic in the actions workflow.
If you can't run the same scripts locally (minus external hosted service/API) then how do you debug them w/o running the whole pipeline?
I find GitHub Actions abhorrent in a way that I never found a CI/CD system before...
everything is including some crappy proprietary yaml rather than using standard tooling
so instead of being a collection of easily composable and testable bits it's a mess that only works on their platform
That's just the good old Microsoft effect, they have a reverse-midas-touch when it comes to actually delivering good UX experiences.
This is not even specific to GitHub Actions. The logic goes into the scripts, and the CI handles CI specific stuff (checkout, setup tooling, artifacts, cache...). No matter which CI you use, you're in for a bad time if you don't do this.
I agree that that should be reasonable but unfortunately I can tell you that not all developers (including seniors) naturally arrive at such conclusion no.
If anyone has any questions or wants to give it a shot, I work at RWX and would be happy to chat more about it!
Main issue is Rust. Writing catchy headlines about hating something may feel good, but a lot of people could avoid these pains if
- zig cc gets support for new linker flag that Rust requires https://codeberg.org/ziglang/zig/pulls/30628 - rust-lang/libc gets to 1.0 which removes iconv issues for macos https://github.com/rust-lang/libc/issues/3248
Original plan: You have an oven in another building that automatically bakes your cake. But the oven needs a mixer, and every time you bake, it has to wait 2-3 minutes for someone to bring the mixer from storage.
Problem: For one specific oven (Linux ARM), the mixer never arrives. So your cake fails. You keep trying different ways to get the mixer delivered. Each attempt: 2-3 minutes wait.
What you finally do: Stop waiting for the mixer to be delivered. Just mix the batter at home where you already have a mixer. Send the pre-mixed batter to the other building. Now the oven just bakes it - no waiting for the mixer.
Translation: Stop trying to generate files in GitHub Actions (where it takes 2-3 minutes each time). Generate them locally on your computer where you already have the tools. Upload the finished files. GitHub Actions just uses them.
Sometimes "pre-mix the batter at home" beats "wait for the mixer every single time."
But almost every company uses GitHub, and changing to Bitbucket isn't usually viable.
on:
schedule:
- cron: $expressionAs soon as I need more than two tries to get some workflow working, I set up a tmate session and debug things using a proper remote shell. It doesn't solve all the pain points, but it makes things a lot better.
Honestly, this should be built into GitHub Actions.
Before that, most people would avoid Jenkins and probably never try Buildbot (because devs typically don't want to spend any time learning tools). Devs would require "devops" to do the CI stuff. Again, mostly because they couldn't be arsed to make it themselves, but also because it required setting up a machine (do you self-host, do you use a VPS?).
Then came tools like Travis or CircleCI, which made it more accessible. "Just write some kind of script and we run it on our machines". Many devs started using that.
And then came GitHub Actions, which were a lot better than Travis and CircleCI: faster, more machines, and free (for open source projects at least). I was happy to move everything there.
But as soon as something becomes more accessible, you get people who had never done it before. They can't say "it enables me to do it, so it's better than me relying on a devops team before" or "well it's better than my experience with Travis". They will just complain because it's not perfect.
And for the OP's defense, I do agree that not being able to SSH into a machine after the build fails is very frustrating.
(In general, I think a lot of criticisms of GitHub Actions don't consider the fully loaded cost of an alternative -- there are lots of great alternative CI/CD services out there, but very few of them will give you the OS/architecture matrix and resource caps that GitHub Actions gives every single OSS project for free.)
1) New technology comes out, people get excited
2) People start recognising the drawbacks of the technology
3) Someone else makes an improved version that claims to fix all of the issues. GOTO 1
And that’s where there’s a Mac Studio that sits sadly in the corner, waiting for a new check in so it has something to do.
> Sure, I still make fun of the PHP I remember from the days of PHP 4.1, but even then I didn’t hate it. (ref. And “PHP: Training Wheels Without a Bike” is still in the Top 10 of my favorite memes.)
I still use it to build my latest projects, and for me it’s like breathing. Simple and functional. It’s not perfect, but no language is.
or try something like https://docs.dagger.io/use-cases#portable-ci
Granted, if you are working on "Windows 12", you won't be building, installing, testing, and deploying that locally. I understand and acknowledge that "as tight as possible" will still sometimes push you into remote services or heavyweight processes that can't be pushed towards you locally. This is an ideal to strive for, but not one that can always be accomplished.
However, I see people surrender the ability to work locally much sooner than they should, and implement massively heavyweight processes without any thought for whether you could have gotten 90% of the result of that process with a bit more thought and kept it local and fast.
And even once you pass the event horizon where the system as a whole can't be feasibly built/tested/whatever on anything but a CI system, I see them surrendering the ability to at least run the part of the thing you're working on locally.
I know it's a bit more work, building sufficient mocks and stubs for expensive remote services that you can feasibly run things locally, but the payoff for putting a bit of work into having it run locally for testing and development purposes is just huge, really huge, the sort of huge you should not be ignoring.
"Locally" here does not mean "on your local machine" per se, though that is a pretty good case, but more like, in an environment that you have sole access to, where you're not constantly fighting with latency, and where you have full control. Where if you're debugging even a complex orchestration between internal microservices, you have enough power to crank them all up to "don't ever timeout" and attach debuggers to all of them simultaneously, if you want to. Where you can afford to log every message in the system, interrupt any process, run any test, and change any component in the system in any manner necessary for debugging or development without having to coordinate with anyone. The more only the CI system can do by basically mailing it a PR, and the harder it is to convince it to do just the thing you need right now rather than the other 45 minutes of testing it's going to run before running the 10 second test you actually need, the worse your development speed is going to be.
Fortunately, and I don't even how exactly the ratio between sarcasm and seriousness here (but I'm definitely non-zero serious), this is probably going to fix itself in the next decade or so... because while paying humans to sit there and wait for CI and get sidetracked and distracted is just Humans Doing Work and after all what else are we paying them for, all of this stuff is going to be murder on AI-centric workflows, which need tight testing cycles to work at their best. Can't afford to have AI waiting for 30 minutes to find out that its PR is syntactically invalid, and can't afford for the invalid syntax to come back with bad error messages that leave it baffled as to what the actual problem is. If we won't do it for the humans, we'll do it for the AIs. This is definitely not something AI fixes, despite the fact they are way more patient than us and much less prone to distraction in the meantime since from their "lived experience" they don't experience the time taken for things to build and test, it is made much worse and more obvious that this is a real problem and not just humans being whiny and refusing to tough it through.
Took me a while to figure that out. While I appreciate occasional banters in blog articles, this one seems to diverge into rant a bit too much, and could have made its point much clearer, with, for example, meaningful section headers.
No. It's cargo cult science.
But it is true that GitHub Actions don't make it easy to debug (e.g. you can't just SSH into the machine after the build fails). Not sure if it justifies hating with Passion, though.
This way we can test it on local machine before deployment.
Also as other commenters have said - bash is not a good option - Use Python or some other language and write reusabe scripts. If not for this then for the off chance that it'll be migrated to some other cicd platform
Not by GitHub, but isn't act supposed to be that?
Don't put your logic in proprietary tooling. I have started writing all logic into mise tasks since I already manage the tool dependencies with mise. I tend to write them in a way where it can easily take advantage of GHA features such as concurrency, matrixes, etc. But beyond that, it is all running within mise tasks.