Check the shell, and stop one-off issues falling off the board
Two gaps, both found the same way -- by something going wrong quietly.
`gh issue create` does not touch the project board. The issue is created, carries its
labels, and is invisible in the Kanban, which looks exactly like a ticket nobody filed.
On 2026-08-24 eight issues filed as a scripted batch all reached the board and one filed
as a one-off minutes later did not; it surfaced only because someone went looking for it.
A batch carries the board step inside its loop. One-offs are where it slips, so
tools/github/file-issue.sh is for one-offs.
Three things it does that a two-command shell snippet would not:
- Resolves the project, Status field and option ids BY NAME, every run. Caching them
is the obvious optimisation and the wrong one -- a renamed or reordered column would
then have this writing a stale id into the board with no error anywhere.
- Reads the item back. A mutation returning 200 says the request was accepted, not that
the board shows what was asked for; the read-back is the only step that checks the
claim this script exists to make. It is a GraphQL query because REST cannot do it --
the `fields` array REST returns on a project item carries Title and nothing else, so
a REST-only check reports every item's Status as unset.
- Exits 3, loudly, with the issue number on a line of its own, when the issue was
created but the board step failed. That exact combination is the failure being
prevented; it must never be the quiet path.
Shell was the other language here with nothing checking it -- four scripts, one of them
the CI entry point. shellcheck now runs in the Static analysis job over
`git ls-files '*.sh'`, so a script added later is covered without editing the workflow,
and it runs at full severity with `info` included.
That raises two findings today and both are the tool being wrong, so both are answered
with a targeted `disable` carrying its reason rather than by lowering the severity:
run-e2e.sh's `on_signal` is reported as never invoked when it is installed as the INT and
TERM trap eleven lines below it, and the `$names` inside file-issue.sh's queries are
GraphQL variables that must not expand -- expanding them would send the shell's idea of
$owner to the API instead of declaring a parameter. A blanket --severity=warning would
have hidden both, and the next real finding with them.
The gradle step gains `if: !cancelled()` so a shellcheck failure cannot cost the
ktlint/detekt/lint lists -- the same reason that step already passes --continue.
Not covered, deliberately: shellcheck here reads .sh files, not the inline `run:` blocks
in the workflows, where a good deal of this repo's bash actually lives. actionlint does
read them, and finds one pre-existing info-level issue in build.yml. Wiring it in means
pinning a container digest, because every action here is pinned by SHA and actionlint's
usual installer is a curl-pipe-bash off a moving branch. Its own ticket, not this commit.
This commit is contained in:
@@ -156,7 +156,26 @@ jobs:
|
||||
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle.kts', 'gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties') }}
|
||||
restore-keys: gradle-${{ runner.os }}-
|
||||
|
||||
# Shell is the other language in this repo -- four scripts, one of them the CI
|
||||
# entry point itself -- and nothing was checking it. `git ls-files` rather than a
|
||||
# fixed list, so a script added later is covered without editing this workflow.
|
||||
#
|
||||
# Full severity, `info` included. The two findings it raises today are answered
|
||||
# with targeted `disable` directives carrying their reason, the same way
|
||||
# config/detekt/detekt.yml carries only the rules this codebase legitimately
|
||||
# breaks. A blanket --severity=warning would have hidden them and the next real
|
||||
# one alike. shellcheck is preinstalled on the ubuntu runner image; the version is
|
||||
# printed so a finding that appears out of nowhere can be pinned to an upgrade.
|
||||
- name: shellcheck
|
||||
run: |
|
||||
shellcheck --version
|
||||
git ls-files -z '*.sh' | xargs -0 -r shellcheck
|
||||
|
||||
# `!cancelled()` rather than a plain sequence: a shellcheck failure above must not
|
||||
# cost the ktlint/detekt/lint lists. Same reason this step passes --continue -- one
|
||||
# round trip should produce every list, not stop at the first.
|
||||
- name: ktlint, detekt and Android lint
|
||||
if: '!cancelled()'
|
||||
run: ./gradlew :app:ktlintCheck :app:detekt :app:lintDebug --continue --stacktrace
|
||||
|
||||
# The XML matters as much as the HTML: it is the one that can be diffed between
|
||||
|
||||
Reference in New Issue
Block a user