That behaviour is familiar from the layout iterations that typst is using. NOTE: my understanding (below) is still based on Typst usage, not internal knowledge.
First layout iteration:
Context dependent numbering is resolved using initial/stale data
Propagate state updates
Second layout iteration
Context dependent numbering is updated using state information from previous iteration - heading gets the correct number
As usual in typst, if a function panics inside context that whole context block fails (produces empty output) but compilation continues. Typst will try again in the next iteration.
However if the context block contains something that the second iteration needs, then the second iteration will fail the same way as the first.
Errors from inside context are only presented to the user if they remain in the last layout iteration - so you never see the errors that resolve themselves.
Conclusion: in typst 0.14.0-rc.2, the counter(heading).step() probably never happens if the numbering function call fails. For some reason they are linked, as if they are inside the same context block.