GitHub Agentic Workflows

One Small Error Message, One Big Feedback Loop

Most bug reports start with a person noticing something odd. This one started with a small scheduled script.

On August 23, the Daily Safe Outputs Conformance Checker spotted a possible gap in gh-aw’s MCP error handling. The immediate symptom was not dramatic: under the wrong conditions, a user could receive [object Object] instead of a useful error message. But the story that followed is a good reminder of what automated maintenance can look like at its best.

One check became issue #55014, a focused repair in PR #55042, and a preventative lint rule proposed in PR #55052. The interesting part is not any one of those artifacts. It is the loop between them.

Every day, the checker runs scripts/check-safe-outputs-conformance.sh against the Safe Outputs implementation. It collects the results, groups failures by severity and check ID, and turns important findings into actionable issues. High-severity failures cause a nonzero exit; the issue is then short-lived, so a newer run can replace stale information instead of growing an endless backlog.

Run #32621246743 raised MCE-006, the check for readable serialized error messages. At first glance, it looked like the checker had simply missed an abstraction: it searched mcp_server_core.cjs for direct calls such as String(e.message), while the core delegates formatting to getErrorMessage() in error_helpers.cjs.

That could have been the end of the investigation: another false positive to tune away. Instead, the generated issue followed the helper. It uncovered a real edge case. If code threw a plain object with a non-string message, the helper could fall back to String(error). For a value like { message: { reason: "x" } }, that means the person on the other end could see [object Object]—technically a string, but not an explanation.

PR #55042 makes the intent explicit: when a non-Error object has a message property, preserve a string message or coerce that message value. Only objects without a message use the whole-object fallback. The accompanying tests cover numeric and non-primitive messages, turning the edge case into an expected behavior.

The repair also improves MCE-006 itself. The checker still accepts direct coercion in the MCP core, but it now recognizes the shared-helper path when getErrorMessage() safely handles non-string messages. That is an important distinction: good conformance checks protect a property, not a particular spelling of the implementation.

Then ask: where else does this pattern live?

Section titled “Then ask: where else does this pattern live?”

The repair was not treated as a one-off. The scheduled ESLint Miner mines recent issues and discussions, scans actions/setup/js, selects one low-false-positive rule, validates it, and opens at most one draft PR. Its August 23 run used MCE-006 as the seed for a broader question: is this pattern hiding elsewhere?

The result is the proposed no-string-fallback-for-non-string-message rule in PR #55052. It looks for a narrow shape: code confirms that x.message is a string, returns it when it is, then falls back to String(x) instead of String(x.message). The rule is a warning, not an automatic rewrite, because a readable fallback still needs local judgment.

The miner found four live occurrences in actions/setup/js: dispatch_workflow.cjs, route_slash_command.cjs, log_parser_shared.cjs, and safeoutputs_cli.cjs. Each deserves its own fix decision. The rule simply ensures that this particular sharp edge is no longer invisible.

The lasting outcome here is not only a better error message. It is a maintenance system that keeps learning: a specification defines the promise, a daily check tests it, an issue investigates the signal, a small repair closes the gap, and a lint rule helps prevent the pattern from returning.

That is the kind of automation worth building. It does not replace engineering judgment; it creates more opportunities to apply it where it matters most. Follow github/gh-aw for the status of the repair and rule proposals, and inspect the linked workflows to adapt this feedback loop in your own repository.