TL;DR: Two small bugs from building the infrastructure toolchain, each the same shape: the tool has a rule you did not know, and its error does not tell you the rule. A GitLab CI job with no rules: block silently skips merge-request pipelines. An OpenTofu import block only works in the root module, never in a child module. Each cost more time than it should have, and each is worth knowing before it costs you any.

Bug one: the rule-less job that skips your merge requests

The cicd gate components, in their first cut, shipped with no rules: block. They were straightforward jobs: lint, scan, validate. No conditions, because they should just always run.

They ran on branch pipelines. On merge requests, they did not run at all. The gates that were the entire point of the components were simply absent from the merge request where you most wanted to see them.

The cause is a GitLab CI rule that is easy to never learn: a job with no rules: block runs only on branch and tag pipelines. It does not run on merge-request pipelines. “No conditions” does not mean “runs everywhere.” It means “runs everywhere except a merge request.”

The fix is faintly absurd, and that is what makes it memorable. You add an unconditional rule: rules: [{ when: on_success }]. The content of that rule does nothing. It always matches. What matters is that the job now has a rules: block at all, because having one is what makes a job eligible for merge-request pipelines. A rule whose content is meaningless, added solely so the block exists, is the fix.

Bug two: the import block that only works at the root

The second bug came from terraform-aws-security-baseline. The account-hardening module needed to adopt a resource that already existed in the account, which is what OpenTofu’s import {} block is for. So an import block went into the account-hardening module, next to the resource it was adopting. The natural place for it.

OpenTofu rejected it. The rule: an import block is only allowed in the root module. It cannot live inside a child module. A module that wants one of its resources imported cannot declare that import itself; the import has to be declared up at the root, and the root caller performs the adoption.

The fix was to take the import block out of the module and document caller-side adoption instead: the module describes the resource, and the root configuration that calls the module is where the import lives.

The shape they share

Two unrelated bugs, in two different tools, and the same shape underneath both.

In each case the tool has a hard structural rule. Where a block is allowed to live. What makes a job eligible for a kind of pipeline. And in each case the error told you the tool was unhappy without telling you which rule you had broken, so the obvious next move, debugging your own logic, was the wrong move. There was nothing wrong with the logic. The thing was simply in a place the tool does not allow, or missing a block the tool quietly requires.

The lasting lesson is not the two specific rules, useful as they are. It is the reflex. When something that should obviously work does not, and the error is unhelpful, stop debugging your logic and suspect a structural rule about where something is allowed to be, or whether a thing is eligible at all. GitLab CI and OpenTofu both have a handful of those, and you mostly learn them by tripping over them. Knowing the shape of the category means the next one costs you an hour instead of an afternoon.

The upshot

Two bugs from building the toolchain, one shape. A GitLab CI job with no rules: block runs on branches and tags but silently not on merge requests, and the fix is to add an unconditional rules: block whose content does nothing and whose mere existence is the point. An OpenTofu import block is rejected inside a child module, because imports are only legal at the root, so the caller adopts and the module just describes.

Neither error named the rule it was enforcing. That is the category to recognise: when sound logic fails against an unhelpful error, suspect a structural rule about where a thing may live or whether it is eligible, not a bug in what you wrote.