README
¶
Setup Action
This action copies workflow script files to the agent environment.
Description
This action runs in all workflow jobs to provide scripts that can be used instead of being inlined in the workflow. This includes scripts for activation jobs, agent jobs, and safe-output jobs.
The action copies runtime .cjs JavaScript files (excluding tests) from the js/ directory and .sh shell scripts from the sh/ directory.
All files are copied to the destination directory (default: ${{ runner.temp }}/gh-aw/actions). These files are generated by running make actions-build and are committed to the repository.
Usage
- name: Setup Scripts
uses: ./actions/setup
with:
# Destination directory for script files
# Default: ${{ runner.temp }}/gh-aw/actions
destination: ${{ runner.temp }}/gh-aw/actions
Inputs
destination
Optional Destination directory for script files.
Default: ${{ runner.temp }}/gh-aw/actions
Outputs
files-copied
The number of runtime JavaScript, JSON, and shell files copied to the destination directory.
Example
steps:
- uses: actions/checkout@v7
with:
sparse-checkout: |
actions
- name: Setup Scripts
uses: ./actions/setup
with:
destination: ${{ runner.temp }}/gh-aw/actions
Files Included
This action copies files from actions/setup/, including:
JavaScript Files
- Activation job scripts (check_stop_time, check_skip_if_match, check_command_position, etc.)
- Agent job scripts (compute_text, create_issue, create_pull_request, etc.)
- Safe output scripts (safe_outputs_, safe_inputs_, messages, etc.)
- Prompt rendering scripts (
create_prompt.cjs,interpolate_prompt.cjs, etc.) - Utility scripts (sanitize_, validate_, generate_*, etc.)
Shell Scripts (7 files from sh/)
create_gh_aw_tmp_dir.sh- Creates temporary directory structurestart_mcp_scripts_server.sh- Starts MCP Scripts HTTP serverprint_prompt_summary.sh- Prints prompt summary to logsgenerate_git_patch.sh- Generates git patchescreate_cache_memory_dir.sh- Creates cache-memory directoryvalidate_multi_secret.sh- Validates that at least one secret from a list is configured
All files are copied from the committed js/ and sh/ directories which contain the source of truth for all JavaScript and shell scripts.
Development
Structured issue reports
All JavaScript GitHub issue creators publish through js/issue_report.cjs.
createReportIssue(client, parameters, report?) renders the report AST before
calling the supplied authenticated client's issue API. Existing issue Markdown
is imported when no document is supplied. GraphQL work-queue creation uses the
same renderIssueParameters boundary, as do staged safe-output previews.
Linear uses the Markdown renderer. Jira uses text-preserving normalization before
converting its literal description to ADF, without replacing ordinary language
characters with visually similar Latin letters.
js/report_ast.cjs defines the typed inline and block nodes, normalization, and
GitHub Markdown renderer. The renderer owns escaping, HTTPS link validation,
heading levels, balanced details, code fences, and whitespace. It rejects raw
HTML/Markdown nodes and redacts diagnostic secrets before choosing delimiters.
js/failure_report.cjs builds reports from md/agent_failure_issue.md.
js/report_markdown.cjs imports the runtime template dialect: headings,
paragraphs, emphasis, links, images, bare HTTPS URLs, code, lists, task lists,
tables, quotes, alerts, separators, and details. Link destinations retain balanced
parentheses, and image sources use the same HTTPS validation as links.
Source paragraph newlines and explicit Markdown hard breaks use typed break nodes;
newlines in scalar metadata still normalize to spaces.
Templates are parsed before scalar metadata is substituted. Diagnostic
fragments enter through explicit block fields, with headings below the report
sections; unsupported HTML remains literal text. New programmatic report content
should use AST nodes rather than concatenated Markdown. Empty optional blocks
are omitted and rendering is deterministic. Complete fragments are redacted before
tokenization, and URL-domain policies apply to fully normalized content.
js/report_footer.cjs builds the footer from the shared attribution template and
model/cost metadata. Attribution, expiration, provenance, failure-match markers,
and the detection preamble are rendered with the document, not appended afterward.
Marker names and fields are validated; marker values cannot close an HTML comment
or inject another metadata field. Ordinary double hyphens in identifiers remain
unchanged. Unknown placeholders in user-configured footer templates remain literal;
internal templates retain strict missing-field validation. Expiration remains a
checked task-list item so existing cleanup and deduplication readers continue to work.
The issue publication importer explicitly enables typed metadata comments to preserve grouping, execution provenance, managed islands, and work-queue IDs. Diagnostic imports do not enable them. JSON metadata is serialized without HTML delimiters while preserving decoded values. Configured mentions use explicit mention nodes after the existing safe-output allowlist and budget checks; other text cannot create notifications. Body limits apply after rendering, including escaping and attribution. A repository-wide regression guards against direct REST issue-creation calls outside the shared publisher.
Special syntax has semantic nodes rather than interpolated strings:
closing_reference records the closing action, issue number, optional
owner/repo, and an explicit enabled flag. Disabled references render an
escaped #, including in PR fallback issues; scalar text never becomes an active
closing directive. structured_data accepts JSON values and renders a safely
fenced JSON block. xml_marker holds a validated name, scalar value, and optional
key/value fields; json_marker holds an actual data object, not serialized JSON.
Footer provenance uses the same XML-marker node and renderer. Structured data
preserves entity-looking strings, redacts secrets, and rejects cycles, getters,
non-finite numbers, and other values JSON would silently drop or coerce.
JSONL runtime helpers
Use js/jsonl_helpers.cjs instead of splitting files or maintaining chunk buffers:
const fs = require("node:fs");
const { readJsonlFile, streamJsonl } = require("./jsonl_helpers.cjs");
for (const record of readJsonlFile(filePath)) {
consume(record);
}
for await (const record of streamJsonl(fs.createReadStream(filePath))) {
await consume(record);
}
Both iterators are lazy, retain at most the current line and input chunk, and close
their input on early termination. Multi-megabyte lines, split UTF-8 characters,
CRLF, and a final line without a newline are supported. Blank and malformed lines
are skipped; obviously non-JSON lines bypass JSON.parse. All JSON values are
supported, including null, false, and 0.
iterateJsonl(textOrLines, options) handles existing text or complete-line iterables.
All JSONL iterators accept lineFilter(line, lineNumber), lineTransform(line, lineNumber), optional maxLineLength
(UTF-16 code units, unlimited by default), and onError(error, lineNumber).
Errors have sanitized messages and physical, one-based line numbers. Filter,
callback, and I/O failures propagate. The length limit rejects oversized records
after framing; memory remains proportional to the largest line.
The filter receives trimmed text before decoding. The transform runs afterward on
trimmed text, allowing timestamp prefixes or keyed framing to be removed without
changing the event's original rawLine.
For an exact top-level field/value search, pass
fieldFilter: { field: "type", value: "wanted" }. All value/event iterators and
JsonlParser support this option. A fast string search skips records that cannot
match before JSON.parse; decoded candidates must have their own matching field.
Values may be strings, finite numbers, booleans, or null. Escaped keys/values
and alternate numeric spellings still match correctly. Filtering occurs after
lineTransform, and filtered records are omitted, not validated. Do not use this
option when every record must be validated or retained for redaction.
let found = false;
for (const record of readJsonlFile(filePath, {
fieldFilter: { field: "type", value: "wanted" },
})) {
found = true;
break; // No later records are decoded; the file closes immediately.
}
Use the same option with iterateJsonl(text) or streamJsonl(chunks).
String inputs also short-circuit before line scanning when no match is possible.
Pass schema to validate decoded records using the lightweight memory schema
subset: type, enum, required, properties, additionalProperties: false,
items, oneOf, and anyOf. Unsupported schemas fail before reading input.
Schemas are prepared once per iterator or persistent parser; numeric enums also
check raw numeric tokens for rounding, without constraining unrelated numbers.
Schema failures are invalid events with reason "schema" and
validationError: { path, message }. Value iterators skip them unless
strict: true is set. Filters still omit records before validation.
For example: readJsonlFile(filePath, { schema: { type: "object" }, strict: true }).
Set fatalUTF8: true on byte-backed file, streaming, or persistent parsers to
report malformed UTF-8 as invalid events with reason "encoding", preserving
physical line numbers. Set jsonWhitespaceOnly: true to reject non-JSON framing
whitespace such as BOM and NBSP. Memory schema validation enables both options;
the default tolerant log parsing behavior is unchanged.
For redaction or diagnostics that must retain rejected input, use
iterateJsonlEvents(textOrLines), readJsonlFileEvents(filePath), or
streamJsonlEvents(chunks). Each event has type ("json" or "invalid"),
rawLine (including its original LF/CRLF terminator, if present), and lineNumber.
JSON events also have value; invalid events have a sanitized error and a
reason ("syntax", "too-long", "blank", "schema", or "encoding"). Blank lines are emitted but do
not invoke onError. No synthetic empty event follows a final newline.
Without a filter, concatenating rawLine reproduces the original text exactly.
Raw lines and schema paths are untrusted and may contain secrets; redact them before logging.
const { iterateJsonlEvents, readJsonlFileEvents } = require("./jsonl_helpers.cjs");
for (const event of iterateJsonlEvents(text)) {
writeRedacted(event.type === "json" ? redactJson(event.value) : redactText(event.rawLine));
if (shouldStop()) break;
}
for (const event of readJsonlFileEvents(filePath)) {
if (isWanted(event)) {
consume(event);
break; // Stops decoding and closes the file immediately.
}
}
The event iterators support break, return, and explicit iterator .return();
they do not decode later records, even in an already-read chunk. Async iterators
also close their upstream source. Value-only iterators accept { strict: true }
to throw on any invalid record, including blank lines, instead of skipping it.
Create one iterator per input; do not restart a parser for each manually split line.
There is no exported standalone record decoder.
Ledger, work-queue, and repo-memory JSONL readers use these shared iterators.
File-backed usage and audit ledgers stream directly from disk. Queue readers
retain bounded buffers where transport-wide UTF-8 validation is required and
apply parseStrictJSON to each original rawLine, preserving duplicate-key,
numeric, Unicode, and resource checks. Shared framing does not make corrupted
queue history skippable or replace canonical-byte and hash verification.
Callback-driven protocols use one persistent JsonlParser per stream:
const { JsonlParser } = require("./jsonl_helpers.cjs");
const parser = new JsonlParser();
source.on("data", chunk => {
for (const event of parser.push(chunk)) consumeEvent(event);
});
source.on("end", () => {
for (const event of parser.finish()) consumeEvent(event);
});
push and finish return lazy event iterators. Consume each iterator completely
before feeding more input; stopping an iterator is appropriate only when abandoning
that stream. Invalid protocol events can be rejected using event.error.
JsonlParser, streamJsonlEvents, and streamJsonl also accept an opt-in
maxPendingRecordLength (positive safe integer). It counts raw UTF-16 code units,
including LF/CRLF, per record, not total stream size or UTF-8 bytes. Framing checks
the bound before retaining fragments or joining a complete record, even when a
single chunk contains the whole record. Exceeding it releases pending fragments
and throws a sanitized, terminal RangeError; abandon the parser and terminate
the source rather than expecting an invalid event or calling finish afterward.
The default remains unlimited, and maxLineLength retains its separate
after-framing behavior. The Agy harness and Pi sub-agent runner explicitly use
16,777,216 UTF-16 code units per raw record and terminate children on overflow,
while allowing legitimate multi-megabyte records.
Custom repair or framing formats can use iterateLines(text), readLines(filePath),
streamLines(chunks), or LineFramer.push(chunk) / finish() without duplicating
line buffering. parseJsonlContent remains an eager compatibility wrapper for
callers that require an array; prefer iterators when results need not be retained.
The js/ and sh/ directories contain the source of truth for all JavaScript and shell script files. These files are manually edited and committed to the repository.
To update JavaScript files:
# Edit files in actions/setup/js/
# Format the JavaScript files
make fmt-cjs
# Lint the JavaScript files
make lint-cjs
# Commit the changes
git add actions/setup/js/
git commit -m "Update JavaScript files"
To update shell scripts:
# Edit files in actions/setup/sh/
# Commit the changes
git add actions/setup/sh/
git commit -m "Update shell scripts"
Note: JavaScript and shell files are NOT embedded in the binary. They are copied at runtime by setup.sh from actions/setup/js/ and actions/setup/sh/ to ${{ runner.temp }}/gh-aw/actions where workflows can access them.
The separate MCP Scripts and safe-outputs directories use the explicit MCP_SCRIPTS_FILES and SAFE_OUTPUTS_FILES lists in setup.sh. Update these lists when adding or removing their runtime modules, including transitive local dependencies. Check deployment completeness with:
cd actions/setup/js
npx vitest run setup_sh_file_lists.test.cjs
MCP gateway step summaries
Gateway summaries share a 1000 KiB UTF-8 byte budget, including any existing
content in the step summary file, below GitHub Actions' 1024 KiB limit.
Oversized sections become bounded, collapsed diagnostic previews with an explicit
truncation notice. Each section receives a share of the remaining budget so large
logs do not crowd out rate-limit, steering, or filtering diagnostics. Complete
redacted gateway logs remain under mcp-logs/ in the existing agent artifact.
Testing Locally
You can test this action locally using the provided test script:
./test-setup-local.sh
This script will:
- Check if
js/directory exists (runsmake actions-buildif needed) - Run setup.sh with test configuration
- Verify all files are copied correctly
Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
var SessionParserSources embed.FS
SessionParserSources contains the runtime parsers and their transitive dependencies, not test fixtures. The CLI uses the same sources as Actions.
Functions ¶
This section is empty.
Types ¶
This section is empty.