fix(runner): do not group a single task's output in CI - #448
Merged
Conversation
A serial or parallel executable wrapped its whole run in a GitHub log group named after the ref. GitHub log groups are always collapsed and offer no way to default one open, so a single-task run hid the only output worth reading behind a click while gaining no structure — `flow test unit` showed a group header and nothing else until expanded. The group is now emitted only when there is more than one task. EndGroup is guarded by the same condition rather than relying on the logger to ignore an unmatched call, so this is correct against the currently pinned tuikit as well as the version that hardens it (flowexec/tuikit#113). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R328pa3FUUfga4gYah1iQi
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
jahvon
added a commit
to flowexec/action
that referenced
this pull request
Aug 27, 2026
Companion to flowexec/tuikit#113 and flowexec/flow#448, which fix the log grouping this works around. # Summary When a flow executable failed, the reason was hard or impossible to find from the run page. Three changes: **1. stderr was thrown away.** `execute.sh` captured it to a temp file, mined it for a JSON error code, and `rm -f`'d it without ever printing it. Anything flow wrote to stderr was lost outright — most importantly the structured error envelope under `--output json`, whose message never reached the log. It is now echoed through. **2. The annotation reported only an exit code.** It now carries the error code and message when flow emits a structured envelope: ``` ::error::flow test unit failed with exit code 1 (EXECUTION_FAILED): unable to parse file - 14:1 ``` Newlines are folded to spaces, since a literal newline ends a workflow command early. **3. A step summary is written for both outcomes.** > ### ❌ `flow test unit` > > Exit code `1` · `EXECUTION_FAILED` > ``` > unable to parse file - 14:1 > ``` # Why this matters flow wraps a serial/parallel run's output in a GitHub log group, and groups are **always collapsed** — GitHub offers no way to default one open. So the actual output sits one click away, and on failure there was nothing outside the group saying what went wrong. Annotations and the step summary render on the run page itself, where a collapsed group does not. flowexec/tuikit#113 and flowexec/flow#448 stop burying single-task output in a group in the first place. These changes are complementary: they make failures visible regardless of how the output is grouped. # Testing The action has no CI workflows to add cases to, so I exercised `execute.sh` directly against a stand-in `flow` binary, checking `GITHUB_OUTPUT` and `GITHUB_STEP_SUMMARY`: | Case | Result | |---|---| | Success | Clean ✅ summary, no annotation, exit 0 | | Failure with JSON envelope | stderr printed; annotation carries code + message; summary shows both; `error-code` output still set | | Failure with plain multi-line stderr | stderr printed (**previously lost entirely**); annotation stays single-line; summary shows exit code | `bash -n scripts/execute.sh` is clean. The `capture`/`upload` path and the `continue-on-error` path are unchanged. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
jahvon
added a commit
to flowexec/tuikit
that referenced
this pull request
Aug 27, 2026
Part of making flow's CI logs readable — see flowexec/flow#448 and flowexec/action#3. # Summary Three problems with CI log grouping, all of which put output where a reader cannot see it. **1. Workflow commands were emitted on every CI, not just GitHub.** `isCI()` is true when `CI=true`, so GitLab, CircleCI, Buildkite and friends printed literal `::group::build app` lines instead of a header. `::group::` is GitHub syntax, not a CI convention — it is now gated on `GITHUB_ACTIONS`, and every other provider gets the styled header that local runs already got. `isCI()` had no other callers, so it is removed rather than left dead. **2. Nested groups produced invalid output.** GitHub does not support nesting: a second `::group::` before the first closes swallows the rest of the log. Depth is now tracked so only the outermost pair is emitted, and a nested caller degrades to no group rather than a broken one. **3. An unmatched `EndGroup` emitted a stray `::endgroup::`**, which closes whatever GitHub had open around it. It is now a no-op. Group names are also escaped per GitHub's spec — an unescaped newline previously ended the command early and leaked the remainder as ordinary log lines.
jahvon
added a commit
that referenced
this pull request
Aug 27, 2026
Consolidates the three open dependabot PRs into one, plus a tuikit bump dependabot cannot open on its own. # Summary **Dependency bumps** (taken as-is from dependabot — closes #434, closes #435, closes #437): | Dependency | From | To | |---|---|---| | `go.etcd.io/bbolt` | 1.4.3 | 1.5.0 | | `github/codeql-action` | 4.37.3 | 4.37.4 | | `postcss` (in `/docs`) | 8.5.12 | 8.5.25 | **`github.com/flowexec/tuikit` 0.4.1 → 0.4.2** — [released](https://github.com/flowexec/tuikit/releases/tag/v0.4.2) for this change, so dependabot had nothing to open a PR against. It carries two logging fixes: - `::group::` was emitted whenever `CI=true`. It is GitHub syntax, so every other provider (GitLab, CircleCI, Buildkite) printed literal `::group::name` lines instead of a header. Now gated on `GITHUB_ACTIONS`; other providers get the styled header that local runs already got. - Nested groups and unmatched `EndGroup` calls could emit invalid output that swallowed or truncated the surrounding log — GitHub does not support nested groups, and a stray `::endgroup::` closes whatever it had open. # Testing `flow validate` passes — generate, lint, unit + e2e, generated-diff, schema validation. **bbolt backs `pkg/store`**, so the suite was run against it rather than taking a minor bump on the datastore on trust. `pkg/store` and `pkg/cache` both pass. **The tuikit fix was verified through this build**, not just assumed from the version number: ``` $ CI=true GITHUB_ACTIONS=true flow exec --spec '...serial with 2 steps...' ::group::run flow/many ::endgroup:: $ CI=true flow exec --spec '...same...' (no literal workflow commands — was leaking them at v0.4.1) ``` # Related #448 stops flow from wrapping a *single* task's output in a group at all, which is the other half of making CI logs readable. It is independent of this PR — this one only moves the dependency. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01R328pa3FUUfga4gYah1iQi Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Companion to flowexec/tuikit#113 and flowexec/action#3.
Summary
A serial or parallel executable wrapped its whole run in a GitHub log group named after the ref. GitHub log groups are always collapsed, and there is no way to default one open — so a single-task run hid the only output worth reading behind a click, gaining no structure in exchange.
From a real CI run of
test unit:test unitis a serial executable with one step, so the group had exactly one child.Change
Emit the group only when there is more than one task:
EndGroupis guarded by the same condition rather than relying on the logger to ignore an unmatched call, so this is correct against the currently pinned tuikit (v0.4.1) as well as the version that hardens it.Verified
Built and run with
CI=true GITHUB_ACTIONS=true:flow test unit(1 step)::group::test flow/unit— all output collapsed::group::run flow/many…::endgroup::— unchangedflow validatepasses.Related
::group::syntax on other CI providers, prevents invalid nested groups, and makes an unmatchedEndGroupa no-op. Independent of this PR, but it is what makes the guard above belt-and-braces rather than load-bearing.🤖 Generated with Claude Code
https://claude.ai/code/session_01R328pa3FUUfga4gYah1iQi