GitHub Actions GITHUB_STATE: Clean Up Temporary Caption Files Without Sharing Secrets Across Jobs

A post step needs a safe reference, not a secret

Imagine a museum’s caption-quality JavaScript action creates a temporary normalized transcript while running in CI. After it finishes, the action must delete that temporary file so a later job cannot accidentally upload it with public artifacts. The path is created during the main action and must be available to the same action’s post lifecycle. GitHub documents GITHUB_STATE for sharing values between an action’s pre, main, and post entry points. It is not a general-purpose store for the whole workflow, nor a substitute for file-permission boundaries. This example uses hypothetical caption fixtures, not real visitor recordings.

Choose the right environment file for the job

GitHub Actions uses several similarly named files with different audiences. GITHUB_ENV sets environment variables for later steps; GITHUB_OUTPUT records step outputs; GITHUB_STEP_SUMMARY formats a readable job summary. GITHUB_STATE serves a narrower purpose: state passed to the same action’s pre or post code. A cleanup path should usually remain private to the action that created it rather than becoming a job output that downstream automation may accidentally publish. Avoid using one file merely because its name looks familiar; the lifespan and visibility of the value are what matter.

Understand where STATE_ values appear

The GitHub workflow-commands documentation says a value written to GITHUB_STATE becomes an environment variable with the STATE_ prefix for the relevant action lifecycle. For example, a saved key named tempPath is accessible in a later phase as STATE_tempPath. The documentation also notes that where multiple pre/post actions run, the saved value is accessible only in the action that wrote it. Do not assume a separate third-party action or another job will inherit the value. Record the expected boundary in the action’s documentation so maintainers do not try to repair a missing variable with an unsafe global export.

Illustrated workflow for GitHub Actions GITHUB_STATE: Create temporary file; Write state to GITHUB_STATE; Remove only owned path.

Create temporary files under a controlled directory

Allocate a temporary subdirectory owned by the action or runner process, not an arbitrary location from a pull-request title or user-controlled configuration. Generate a unique file name, restrict access where appropriate, and avoid writing raw private transcripts into logs. A caption QA action can safely record a path to a benign fixture, hash result, or local intermediate report while keeping the actual data confined to the runner filesystem. Never trust user-supplied filenames without normalizing and validating them against the allowed directory. Cleanup logic should refuse paths outside the temporary root rather than invoking an unrestricted recursive deletion command.

Write a minimal state record from the action

Within the JavaScript action, use GitHub’s toolkit saveState helper or write a newline-delimited key=value entry to the file named by GITHUB_STATE, following the documented encoding rules. Store the smallest reference necessary for cleanup, such as a generated temporary directory name, not the transcript contents or an access token. If the saved value could contain newlines or special formatting, follow GitHub’s supported environment-file syntax rather than constructing fragile shell text. Errors during state writing should be visible, because a post action that lacks a valid path must not fall back to deleting a broad directory.

Use the post entry point intentionally

JavaScript action metadata can declare main and post lifecycle scripts; review the supported metadata syntax and any post-if condition that controls when cleanup runs. The post code reads the action-local STATE_ value and checks the target path before deleting it. A post phase is helpful even if main processing reports a failure, but do not assume cleanup always runs after abrupt runner termination, power loss, or operating-system failure. Keep secrets and actual private media out of persistent artifacts in the first place. A cleanup hook is defense in depth, not a guarantee that temporary data could never survive a crashed runner.

Conceptual comparison, not an observed vendor output, of Workflow-wide output and Action-local cleanup state.

Compare action state with public job outputs

Consider two signals: a one-word QA verdict for a later release-gate step, and the path to a local file that post must delete. The verdict may belong in GITHUB_OUTPUT under a tightly defined contract. The private cleanup path belongs in GITHUB_STATE because only the creating action needs it. Neither channel should be used to exfiltrate transcript content. Keep the values separate so a future workflow maintainer cannot accidentally attach the cleanup path to a published release record. The illustrations show conceptual state movement, not screenshots of a live workflow.

Test failures and path validation deliberately

On a nonproduction runner with synthetic caption text, exercise a successful action, a main-step exception after creating the temp file, a missing state entry, and an invalid path outside the approved root. The post script should delete only known owned files and report a safe failure when it cannot validate ownership. Avoid printing confidential paths or contents as a debugging shortcut. Inspect whether cleanup should occur when an action is cancelled and document the remaining limitations. A passing test is useful only if it actually covers the lifecycle and failure conditions relevant to the deployed action.

Visual review points for GitHub Actions GITHUB_STATE: Stay inside owned directory; Avoid secrets in state; Inspect post condition.

Keep dependency and permission review separate

State handling does not make an action trustworthy. Pin or review action dependencies, restrict GITHUB_TOKEN permissions, and ensure pull-request code cannot cause a privileged post script to delete arbitrary runner paths. Reusable workflows and externally maintained actions have their own trust boundaries. For self-hosted runners, plan workspace and runner hygiene beyond a single action; post cleanup should not be the sole isolation mechanism. Security review must cover which code executes and whose input it reads, not only whether a STATE_ variable is available.

Write an auditable cleanup contract

Document the temporary directory root, state key, entry points that write and read it, allowed deletion scope, and error behavior when state is missing. Keep the retention of any required QA evidence intentional and separate from temporary workspace cleanup. When the workflow is eventually allowed to deploy a museum website, use an independent human or policy approval gate. GITHUB_STATE can reliably convey action-local housekeeping information within its supported lifecycle, but it cannot approve publication, restore lost files, or certify that a recording has been handled according to consent policy.

How We Research

Our named primary official documentation sources were: GitHub Docs — Workflow commands: Sending values to pre and post actions; GitHub Docs — Metadata syntax for GitHub Actions; GitHub Docs — Security hardening for GitHub Actions. We reviewed GitHub’s environment-file syntax, action-local STATE_ visibility and pre/main/post lifecycle documentation. We did not execute a runner, inspect private caption files, or measure cleanup success in a repository. Check the current documentation, account permissions and model or API version before implementation. The figures are original editorial diagrams rather than live vendor screenshots or measured results.

Frequently Asked Questions

Is GITHUB_STATE the same as GITHUB_ENV?

No. GITHUB_STATE shares data with the same action’s pre/post lifecycle; GITHUB_ENV sets environment variables for later workflow steps.

What variable name does a saved state key use?

A saved key such as tempPath is exposed as STATE_tempPath in the applicable action lifecycle.

Can another action read my GITHUB_STATE value?

GitHub documents action-local visibility; do not rely on other actions or jobs inheriting it.

Should I save a token or transcript body in GITHUB_STATE?

No. Store only minimal, nonsecret cleanup references and keep sensitive data in approved protected locations.

Does a post action always run after a runner crash?

No. A post hook cannot guarantee execution after abrupt infrastructure failure; combine it with runner hygiene and retention controls.

Scroll to Top