What this covers
This was a day spent not on feature code but on maintaining the development environment itself (tooling, build, CI configuration). The surfaces were all different —a hook script wouldn't run, monorepo publishing broke, automated releases stopped running, and I added a new guard for dangerous commands— but a single lesson ran through them all. The first thing to suspect when something "doesn't work" is not a tool bug but the wiring (quoting, version protocols, runner labels).
Here I organize the concepts I met that day purely as general knowledge, unrelated to any specific company or project.
Day summary
| Concept | What the problem was | What I learned |
|---|---|---|
| Shell word splitting & quoting | Value is right, only the command fails | Quote paths with spaces as "$VAR" |
| Monorepo workspace publishing | Internal deps break on external install | workspace:* is rewritten by pnpm publish |
| CI runner mismatch | "Release tool malfunction" | Failure with no logs = runner/permission issue |
| Fail-open guard | The safety device blocks the work | Pass on failure, block only obvious disasters |
1. The value is right but the command fails — shell word splitting
What the situation was. An automatically triggered hook script runs ${PLUGIN_ROOT}/hook.sh, but in an environment where the path contained a space there was no response at all. Printing the variable value clearly showed it was correct, yet only the execution failed.
What I learned. The shell (bash, zsh) expands a variable and then splits the result into multiple arguments on whitespace (word splitting). /tmp/My Files/hook.sh becomes two pieces, /tmp/My + Files/hook.sh, and the script can't be found. Quoting as "${PLUGIN_ROOT}/hook.sh" preserves the value as a single argument. In scripts people rarely look at, like hooks and CI, this mistake breaks silently, so the default is to quote paths and filenames without exception.
→ Shell word splitting and quoting
2. Internal deps break on external install — monorepo workspace publishing
What the situation was. In a monorepo where several packages live in one repository, a published package didn't resolve its dependencies when installed externally. Another package had a nonexistent version pinned in its dependencies, so CI installs died with a 404.
What I learned. A monorepo links internal dependencies as "pkg": "workspace:*" so that local source is reflected immediately. But publishing this workspace:* as-is leaves external users unable to interpret it. pnpm publish (a manager that understands workspaces) rewrites it to a real version at publish time, but npm publish cannot. Publishing must go from the bottom of the dependency tree (the leaves) upward, and because pnpm publish cannot do npm OIDC (tokenless publishing), a publish token is needed.
→ Monorepo workspace protocol and publish rewriting
3. The reality behind a "tool malfunction" is infrastructure — CI runner mismatch
What the situation was. A report that automated releases were "malfunctioning." Yet the failed job had no step logs at all.
What I learned. A CI job picks the runner (the machine that runs the job) to execute on via its runs-on label. If an organization drops cloud-hosted runners and moves to self-hosted, but only the release job keeps the old label (ubuntu-latest), then that job has no runner to assign to, so it can't even start and leaves no logs. It wasn't a tool bug but an infrastructure-wiring problem. For a CI failure with no logs, look first at the runner, permissions, and triggers, not the code. The preventive measure is to align runs-on across the whole repository at once, so you don't miss the leftovers of a partial migration (jobs that run only occasionally).
4. Keeping a safety device from becoming a wall — fail-open guards
What the situation was. I added a new guard hook to block dangerous commands. The problem is that if this guard itself breaks (a dependency tool is missing, parsing fails), it could block the entire shell.
What I learned. When a guard breaks, letting it pass is fail-open, blocking is fail-closed. A guard on a developer-convenience tool should be made fail-open (pass when a dependency is missing) so the safety net doesn't become a wall. Conversely, security boundaries like authentication, payments, and data destruction are fail-closed. Also, a guard should block only obvious disasters — if it blocks even ambiguous cases, false positives are frequent, users turn the guard off, and the whole net disappears. Before adding it to global config, self-check with dangerous and normal cases and leave a backup.