the padded cell

Things I've Learned (TIL)

Things I want to remember and might help others. Quick discoveries from debugging sessions, neat tricks, useful commands, and so on.

20 August, 2025

gum simplifies making shell scripts interactive: no more wrestling with read commands and ANSI escape codes for user input: just proper text editing, defaults, and clean UI.

Their demo: gum gemo

For example, if you run:

gum input --width 50 --header "Favorite author?" \
 --value "Terry Pratchett"

It will give you a question with a sensible default that you can then change as you want, and what you wrote as you hit enter will be returned so your script can then use it.

If you instead has some options where you want to select one:

gum choose --header "Favorite book?" \
 "Hogfather" "Thief of Time" "The Night Watch" "Small Gods"

Which gives you a selection box, you can configure to allow multiple selections, and the one you pick is then returned.

And one I really love, have it show a spinner to indicate that yes… something is still going on:

gum spin --title="Counting down from 10" sleep 10

Built on bubbletea, a Go TUI framework, use that directly if you need these components in Go code rather than shell scripts.

15 August, 2025

If you start a non-interactive bash shell it will source the content of the file defined in BASH_ENV (and ENV for a POSIX shell).

When bash is started non-interactively, to run a shell script, for example, it looks for the variable BASH_ENV in the environment, expands its value if it appears there, and uses the expanded value as the name of a file to read and execute. Bash behaves as if the following command were executed:

if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi

but the value of the PATH variable is not used to search for the file name.

The YAML anchors to name a reusable section are defined by &name and then to use them to replace the value of something you do *name, if you want to “unsplat”/merge a dictionary/object then use <<: *name and then it’ll insert it at that point.

world: &world World
example: &example-anchor
  HELLO: *world
  There: Yo

my-values:
  <<: *example-anchor
  foo: bar

becomes

---
example:
  HELLO: World
  There: Yo
my-values:
  HELLO: World
  There: Yo
  foo: bar

YAML does not support unsplatting lists (basically, merging list items inline like you can with objects) and that’s intentional.

So if you have a document like below, there is no syntax to make the commands a three item list:

---
example: &example
  - "Hello"
  - "World!"

name: "hello"
commands:
  - << *example
  - "Oho!"

WILL NOT turn into:

---
example: &example
  - "Hello"
  - "World!"

name: "hello"
commands:
  - "Hello"
  - "World!"
  - "Oho!"

…and just because before I found the issue where it was described that this isn’t happening I had made a test repo to try and understand if it was a library/usage issue, because I noticed I could get things running on CI with << *example which syntax didn’t give syntax error, but also did nothing when running on Drone CI.

13 August, 2025

/usr/bin/env executes commands with flags/subcommands, not just bare executables. Which is great if you, for example, have a script/lint that’s a Python script, and it needs dependencies from a virtualenv that isn’t active when you call it.

Just put your shebang as /usr/bin/env uv run python3 and it always runs in the virtualenv, no wrapper script needed. This feels obvious in hindsight, it’s what you expect from these tools 😃

02 August, 2025

Drone CI has special handling for ${VAR} and will replace it itself instead of letting the shell do it. So if you have export PATH=${GOPATH}/bin:$PATH and have $GOPATH defined in your global environment key, then it’ll become a blank value. But if you do export PATH=$GOPATH/bin:$PATH it’ll work. You can escape by adding an extra dollar sign, i.e. $${VAR} will be evaluated by the shell.

This is different from normal shell where ${VAR} is the same as $VAR and it exists to make sure you can concatenate strings without issues, i.e.

VAR=m000
echo "'${VAR}est'"  # => 'm000est'
echo "'$VARest'"    # => ''
til Updated #drone-ci

Drone CI will not smartly skip subsequent steps if you have a when on the very first step in a depends_on chain, so you have to repeat the when condition for each step because it doesn’t realize that the first dependency is gone.

Drone CI’s when for deciding in which cases to run steps/pipelines targets the merge target branch for PRs and not the actual PR’s, also it pulls from refs/pull/<num>/head instead of refs/heads/<branch> so you can’t target the branch itself using the pull_request event (use push on the pipeline and then branch for the step instead).

In shell scripts if you do "$@" it will actually expand “quoted sentences” correctly, and if you just do $@ it will always unwrap them into single words, I thought that if you did "$@" it would combine all arguments into a single argument, and what it does is do what I thought $@ alone did.

I.e., with "$@" the arguments "one two" three will be 2 arguments, the first being "one two", and without it will become three arguments, all separated by space.

The <link rel="canonical" href="…"> is a bit like a correlation ID between services and I need also to have it on my canonical page, because if someone sends it with ?utm_source=foo it could be a different page than the one without the query string.

The scripts/commands inside a shell script inherit access to STDIN when you call them, so if you have a shells script that only has cat and you do ./script.sh < script.sh then it’ll output the content of itself

You can view the webhooks that GitHub sends in your project settings, so for example Drone CI getting notified and exactly what information that goes into the payload, and when/if it was sent, is available there.

Go to Settings -> Webhooks and then click on the integration you want to look at.

01 August, 2025

You can enable the fingerprint reader for sudo on macOS, and pressing my finger on a button beats having to type the password, steps:

  1. cp /etc/pam.d/sudo_local{.template,}
  2. Edit /etc/pam.d/sudo_local and uncomment auth sufficient pam_tid.so.

The reason for doing this in sudo_local is that this file will not get reset with system changes from Apple.