npx skills add ...
npx skills add github/gh-aw --skill messages
Add new safe-output message types and wire validation/rendering.
npx skills add github/gh-aw --skill messages
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:
Key points:
kebab-case for the YAML field name (for example my-new-message)Add the field to SafeOutputMessagesConfig in pkg/workflow/compiler.go:
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/:
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:
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.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:
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/.
type SafeOutputMessagesConfig struct {
// ... existing fields ...
MyNewMessage string `yaml:"my-new-message,omitempty" json:"myNewMessage,omitempty"`
}// @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,
};import { 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");
});
});const { getMyNewMessage } = require("./messages_my_new.cjs");
const message = getMyNewMessage({
placeholder1: actualValue1,
placeholder2: actualValue2,
});| Module | Purpose | Exported Functions |
|--------|---------|-------------------|
| `messages_my_new.cjs` | My new message description | `getMyNewMessage` |