Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Add new safe-output message types and wire validation/rendering.
.claude/skills/github-messages/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-17 | ✗→✓ | ▲ Improved | -38% | 0% |
| case-01 | ✗→✓ | ▲ Improved | -2% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 15% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 312% | 0% |
| case-08 | ✗→✓ | ▲ Improved | -4% | 0% |
Use this guide to add a new safe-output message type so it works in the current gh-aw pipeline: frontmatter → schema → Go compiler → JavaScript modules → action/workflow build output.
The messages system lets workflow authors customize safe-output messages. The current architecture does not rely on the old pkg/workflow/js.go embedding registry for runtime shipping.
Current flow:
pkg/workflow/js/ or actions/setup/js/make actions-build or the relevant workflow build pathAdd the new message field to pkg/parser/schemas/main_workflow_schema.json in the messages object:
json{ "messages": { "properties": { "my-new-message": { "type": "string", "description": "Description of when this message is used. Available placeholders: {placeholder1}, {placeholder2}.", "examples": [ "Example message with {placeholder1}" ] } } } }
Key points:
kebab-case for the YAML field name (for example my-new-message)Add the field to SafeOutputMessagesConfig in pkg/workflow/compiler.go:
gotype SafeOutputMessagesConfig struct { // ... existing fields ... MyNewMessage string `yaml:"my-new-message,omitempty" json:"myNewMessage,omitempty"` }
Key points:
CamelCase for Go field nameskebab-case for YAML tagscamelCase for JSON tagsomitempty to both tagsIf the message needs custom parsing logic, update the workflow parser in pkg/workflow/safe_outputs.go or the relevant config block. Most simple string fields will be wired automatically by the existing reflection-based parser.
Create the new module in the current shared JS location, typically pkg/workflow/js/:
javascript// @ts-check /// <reference types="@actions/github-script" /> const { getMessages, renderTemplate, toSnakeCase } = require("./messages_core.cjs"); /** * @typedef {Object} MyNewMessageContext * @property {string} placeholder1 - Description of placeholder1 * @property {string} placeholder2 - Description of placeholder2 */ function getMyNewMessage(ctx) { const messages = getMessages(); const templateContext = toSnakeCase(ctx); const defaultMessage = "Default message with {placeholder1} and {placeholder2}"; return messages?.myNewMessage ? renderTemplate(messages.myNewMessage, templateContext) : renderTemplate(defaultMessage, templateContext); } module.exports = { getMyNewMessage, };
Key points:
messages_<category>.cjs./messages_core.cjs for shared helpersCreate a matching test file, for example pkg/workflow/js/messages_my_new.test.cjs:
javascriptimport { describe, it, expect, beforeEach, vi } from "vitest"; const mockCore = { warning: vi.fn() }; global.core = mockCore; describe("getMyNewMessage", () => { beforeEach(() => { vi.clearAllMocks(); delete process.env.GH_AW_SAFE_OUTPUT_MESSAGES; }); it("returns the default message when no custom template is configured", async () => { const { getMyNewMessage } = await import("./messages_my_new.cjs"); const result = getMyNewMessage({ placeholder1: "value1", placeholder2: "value2" }); expect(result).toBe("Default message with value1 and value2"); }); it("uses the custom template when configured", async () => { process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ myNewMessage: "Custom: {placeholder1}" }); const { getMyNewMessage } = await import("./messages_my_new.cjs"); const result = getMyNewMessage({ placeholder1: "test", placeholder2: "ignored" }); expect(result).toContain("Custom: test"); }); });
Run the relevant tests with make test-js or the targeted Vitest file.
Update the SafeOutputMessages typedef and the return object in pkg/workflow/js/messages_core.cjs, and re-export the message helper from pkg/workflow/js/messages.cjs.
Do not add any new //go:embed entries to pkg/workflow/js.go for a normal message module. The current system packages JavaScript through the action-generation/build path.
Instead:
pkg/workflow/js/ or the relevant action folder,make actions-build.javascriptconst { getMyNewMessage } = require("./messages_my_new.cjs"); const message = getMyNewMessage({ placeholder1: actualValue1, placeholder2: actualValue2, });
Document the new message in the repo’s relevant safe-output docs, and keep the examples aligned with the current action-based JavaScript build flow.
Before committing a message change:
messages_core.cjs and messages.cjs updated if relevantactions/README.md - current action-generation/build workflowpkg/workflow/js/messages_core.cjs - shared safe-output message helperspkg/workflow/js/messages.cjs - message exportspkg/parser/schemas/main_workflow_schema.json - schema source of truthUpdate the Message Module Architecture table:
markdown| Module | Purpose | Exported Functions | |--------|---------|-------------------| | `messages_my_new.cjs` | My new message description | `getMyNewMessage` |
For current gh-aw work, keep message modules aligned with the action-generation flow instead of the historical Go-embed pattern. If you need an example, review the existing safe-output modules under pkg/workflow/js/ and the generated action files under actions/.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-16 | pass→pass | 15,030 | 7,004 | -53% | 1 | 1 | 0% | 1,660 | 2,015 | +21% | 0 | 0 | — |
case-17 | fail→pass | 23,857 | 7,129 | -70% | 1 | 1 | 0% | 3,258 | 2,021 | -38% | 0 | 0 | — |
case-01 | fail→pass | 26,762 | 16,958 | -37% | 1 | 1 | 0% | 4,277 | 4,212 | -2% | 0 | 0 | — |
case-02 | fail→fail | 27,689 | 20,199 | -27% | 1 | 1 | 0% | 4,179 | 5,347 | +28% | 0 | 0 | — |
case-03 | fail→pass | 25,284 | 17,625 | -30% | 1 | 1 | 0% | 3,883 | 4,479 | +15% | 0 | 0 | — |
case-04 | pass→pass | 11,754 | 9,979 | -15% | 1 | 1 | 0% | 1,373 | 2,601 | +89% | 0 | 0 | — |
case-05 | pass→pass | 16,153 | 10,761 | -33% | 1 | 1 | 0% | 2,074 | 3,726 | +80% | 0 | 0 | — |
case-06 | pass→pass | 17,038 | 15,933 | -6% | 1 | 1 | 0% | 2,213 | 3,782 | +71% | 0 | 0 | — |
case-07 | fail→pass | 8,638 | 8,460 | -2% | 1 | 1 | 0% | 577 | 2,378 | +312% | 0 | 0 | — |
case-08 | fail→pass | 18,364 | 8,303 | -55% | 1 | 1 | 0% | 2,405 | 2,315 | -4% | 0 | 0 | — |
case-09 | fail→pass | 15,253 | 11,666 | -24% | 1 | 1 | 0% | 1,620 | 3,024 | +87% | 0 | 0 | — |
case-10 | fail→pass | 19,462 | 8,826 | -55% | 1 | 1 | 0% | 2,136 | 2,527 | +18% | 0 | 0 | — |
case-11 | fail→pass | 14,053 | 8,367 | -40% | 1 | 1 | 0% | 1,487 | 2,370 | +59% | 0 | 0 | — |
case-12 | fail→pass | 12,207 | 8,887 | -27% | 1 | 1 | 0% | 1,232 | 2,445 | +98% | 0 | 0 | — |
case-13 | fail→pass | 17,076 | 7,877 | -54% | 1 | 1 | 0% | 1,922 | 2,209 | +15% | 0 | 0 | — |
case-14 | fail→pass | 19,653 | 10,235 | -48% | 1 | 1 | 0% | 2,639 | 2,691 | +2% | 0 | 0 | — |
case-15 | fail→pass | 17,475 | 7,092 | -59% | 1 | 1 | 0% | 1,783 | 2,010 | +13% | 0 | 0 | — |
case-18 | fail→pass | 18,121 | 4,838 | -73% | 1 | 1 | 0% | 2,009 | 2,605 | +30% | 0 | 0 | — |
case-19 | pass→pass | 15,063 | 9,118 | -39% | 1 | 1 | 0% | 2,159 | 2,473 | +15% | 0 | 0 | — |
case-20 | fail→pass | 18,406 | 11,135 | -40% | 1 | 1 | 0% | 1,965 | 2,877 | +46% | 0 | 0 | — |
case-21 | fail→pass | 28,831 | 7,393 | -74% | 1 | 1 | 0% | 4,144 | 2,134 | -49% | 0 | 0 | — |
case-22 | pass→pass | 14,107 | 7,522 | -47% | 1 | 1 | 0% | 1,633 | 2,223 | +36% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +68 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 8/9/2026 | +64% |
Other measured skills in the registry, with their headline benchmark lift.