Skip to content

docs(workflow_yaml) + test(workflow_hardening): document the reader; clear reused temp repos - #1027

Merged
hyperpolymath merged 4 commits into
mainfrom
claude/eager-wright-zwwbe2-workflow-yaml-docs
Nov 9, 2026
Merged

hyperpolymath merged 4 commits into
mainfrom
claude/eager-wright-zwwbe2-workflow-yaml-docs

Conversation

@hyperpolymath

@hyperpolymath hyperpolymath commented Nov 9, 2026 •

Copy link
Copy Markdown
Owner

Summary

Two small, independent commits that touch the same module family, plus a follow-up for Codex's review. I meant the first two as two stacked PRs, but they landed on one branch. Each commit stands alone and can be reviewed on its own.

1. docs(workflow_yaml): document the shared reader's public API (17731bb)

Adds an @doc to every public function of Hypatia.WorkflowYaml. The module became public in #1005, but most of its functions still carried their docs as # comments from their time as private RE001 helpers, and read_workflow/1 and flow_to_block/1 were @doc false. CodeRabbit's docstring-coverage pre-merge check on #1005 flagged this.

  • Moved verbatim: the existing comment becomes the @doc for step_key_column, job_steps, key_line?, lines_in_steps, step_indent, strip_yaml_comment, workflow_job_lines, read_workflow and flow_to_block.
  • New, short docs for expand_aliases, nesting_at, direct_job_field?, job_field_indent and indent_of.

No code changes.

2. test(workflow_hardening): clear reused temp repos before writing (ae522d3)

WorkflowHardeningTest reuses temporary repository paths across runs without clearing them.

  • The helper names its repositories wh_test_<unique_integer>, and the empty-repo test uses wh_empty_<unique_integer>.
  • System.unique_integer/1 restarts in every VM, so a later run reuses the same names.
  • A run that crashes before its cleanup leaves its workflows in place, and the next run scans them alongside its own fixture. A stale pull_request_target workflow then trips WH001 in unrelated tests. I hit this as a single WH001 failure that wouldn't reproduce.

The fix clears the directory before writing, as ResearchExtensionsTest's helper already does.

3. Codex review follow-up (fbbb573)

  • expand_aliases/1 doc: it now gives the real scope: only the top-level jobs: and env: sections. An alias elsewhere is returned unchanged.
  • nesting_at/2 doc: it now says every bracket counts, so callers pass text with quoted scalars and comments already blanked.
  • Temp names: wh_test_* and wh_empty_* now carry System.pid(), so two concurrent mix test runs on one TMPDIR never share a path that the new rm_rf! would clear.

Type of change

  • 📝 Documentation
  • 🧪 Test fix (local re-run isolation)

How has this been verified?

Check main This PR
workflow_hardening_test with 2,000 stale wh_test_* + wh_empty_* dirs planted under TMPDIR 24 of 104 fail 0 of 104
Same, clean TMPDIR 0 of 104 0 of 104
research_extensions_test + workflow_hardening_test 222/222 222/222 (after fbbb573: twice more)
  • Docs present: Code.fetch_docs(Hypatia.WorkflowYaml) shows a doc for every public function. strip_yaml_comment/1 shares the /2 entry, as Elixir does for default arguments.
  • Build and format: workflow_yaml.ex compiles with elixirc --warnings-as-errors, standalone Elixir 1.14 (hex.pm is unreachable here). mix format --check-formatted is clean.
  • CI is unaffected by the test fix, because runners start fresh. The fix guards local re-runs.

Follow-up (not in this PR)

ResearchExtensions passes raw on: flow text to nesting_at/2 when it reads an on: collection that spans several lines. That is a pre-existing caller of the contract now documented above.

Checklist

  • My commits are signed. Signing is handled by the session's platform shim.
  • I ran the project's own checks/tests locally and they pass (scoped as above).
  • Docs are updated (commits 1 and 3).

https://claude.ai/code/session_01CYR5bf5FBLS7cXQK72aVNM

Hypatia.WorkflowYaml became a public module in #1005, but most of its
functions still carried their documentation as `#` comments from their
time as private RE001 helpers, and two were `@doc false`. CodeRabbit's
docstring-coverage check on #1005 flagged it.

Every public function now has an @doc: the existing comment, moved
verbatim where there was one (step_key_column, job_steps, key_line?,
lines_in_steps, step_indent, strip_yaml_comment, workflow_job_lines,
read_workflow, flow_to_block), and a short new one where there was none
(expand_aliases, nesting_at, direct_job_field?, job_field_indent,
indent_of). No code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYR5bf5FBLS7cXQK72aVNM
@coderabbitai

coderabbitai Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 29 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 0cf7d9fe-0a06-4345-8c9f-a666c45bfef8
Commits

Reviewing files that changed from the base of the PR and between be6510f and a63c7d1.

Files selected for processing: 2

  • lib/hypatia/workflow_yaml.ex
  • test/workflow_hardening_test.exs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

WorkflowHardeningTest names its temporary repositories
`wh_test_<unique_integer>` and `wh_empty_<unique_integer>`.
System.unique_integer/1 restarts with every VM, so a later run reuses
the names an earlier run used. A run that crashed before its cleanup
(for example, one whose modules failed to compile) leaves its workflows
behind, and the next run scans them alongside its own. A stale
pull_request_target workflow then trips WH001 in unrelated tests.

Clear the directory before writing, as ResearchExtensionsTest's helper
already does. With 2,000 stale directories planted under TMPDIR, the
suite fails 24 of 104 on main and 0 of 104 with this change; it also
passes on a clean TMPDIR. CI is unaffected (fresh runners); this guards
local re-runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYR5bf5FBLS7cXQK72aVNM
@hyperpolymath hyperpolymath changed the title docs(workflow_yaml): document the shared reader's public API docs(workflow_yaml) + test(workflow_hardening): document the reader; clear reused temp repos Nov 9, 2026
@hyperpolymath
hyperpolymath marked this pull request as ready for review November 9, 2026 13:06
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ❌ Failed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-09T22:49:58.746026Z a63c7d1 New commits
🔒 Security Review ❌ Failed 2026-11-09T22:45:15.453862Z
We couldn’t complete this review. Comment @codex security review to try again.
a63c7d1 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

This security review couldn’t start because the paying account has exhausted its Codex allowance and has insufficient available credits. The payer can check their balance and add credits in the Codex usage dashboard, then try again. If you do not manage the paying account, contact this repository's admins.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ae522d3bca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/hypatia/workflow_yaml.ex Resolved
Comment thread lib/hypatia/workflow_yaml.ex Resolved
Comment thread test/workflow_hardening_test.exs Resolved
…esting docs; keep concurrent VMs apart

Codex review on #1027 raised three points:

- expand_aliases/1: the new @doc said every alias is replaced in place.
  The code expands only within the top-level jobs: and env: sections,
  so the doc now says so, and says an alias elsewhere is returned
  unchanged.
- nesting_at/2: it counts every bracket character, including those in
  quoted scalars and comments. The doc now says callers pass text with
  those already blanked.
- wh_test_* and wh_empty_* fixtures: unique_integer/1 is unique only
  within one VM, so two concurrent `mix test` runs on one TMPDIR could
  pick the same name, and the new rm_rf! could delete the other run's
  live fixture. The OS pid is now part of the name. The rm_rf! still
  clears what a crashed run with a reused pid left behind.

RE + WH suites: 222/222, twice. workflow_yaml.ex compiles with
--warnings-as-errors. mix format is clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYR5bf5FBLS7cXQK72aVNM
@chatgpt-codex-connector

Copy link
Copy Markdown

This security review couldn’t start because the paying account has exhausted its Codex allowance and has insufficient available credits. The payer can check their balance and add credits in the Codex usage dashboard, then try again. If you do not manage the paying account, contact this repository's admins.

hyperpolymath added a commit that referenced this pull request Nov 9, 2026
…otes (#1039)

## Summary

This is the follow-up named in #1027.

`Hypatia.WorkflowYaml.nesting_at/2` counts every bracket it is given.
Callers must therefore pass text with comments and quoted scalars
blanked, as its doc now says. RE001's trigger reader passed raw text in
three places:

| Where | Input | Effect on main |
|---|---|---|
| Continuation line of a multi-line `on: [...]` | `workflow_dispatch] #
was pull_request_target` | A phantom `pull_request_target` trigger
becomes an untrusted-input **exposure**. |
| Same | `workflow_dispatch] # see [ops notes` | The bracket stays open
and the reader swallows the rest of the file. A `run:
./issue_comment.sh` line then becomes an `issue_comment` trigger. |
| Inline `on: {...}` map | `{push: {tags: ["v1.0]"]}, issue_comment: …}`
| The depth is shifted, so the real top-level `issue_comment` is
**dropped**. |
| A step's flow `with: {...}` | a quoted `]` or `[` | Same miscount when
deciding whether the mapping continues. |

Only whole-line comments are dropped before a rule reads a workflow
(`active_lines/1`). Trailing comments on continuation lines therefore
reached both the bracket count and the event-word scan.

## Fix

- Each continuation line is passed through `strip_yaml_comment/1` before
it is appended. The `on:` line uses the same function instead of a
`\s+#` regex.
- A new `open_flow?/1` reads the depth after blanking quoted scalars
with `strip_yaml_comment(_, true)`. Byte offsets are preserved. Both
continuation readers use it: the `on:` reader and the `with:` reader.
- `inline_events/1` takes key names from the raw map but the depth from
its blanked copy.

## Verification

- Four new RE001 tests:
- a positive control: an `issue_comment` on a continuation line with a
balanced `[bot]` comment is still an exposure;
  - three decoys from the table above.

On `main` the three decoys **fail** and the control passes. On this
branch all four pass.
- `research_extensions_test`: **122/122**. Compiled standalone with
`elixirc --warnings-as-errors` because hex.pm is unreachable here. `mix
format --check-formatted` is clean.
- RE001 findings over five real trees, before and after, are
**identical**: 18 findings across hypatia, standards, echidna,
gitbot-fleet and mathesis.

## Aspect notes

- **Type/proof route:** the cleaner fix is a type distinction between
raw line text and "code" text. A caller could then not hand raw text to
`nesting_at/2` at all. In Elixir that is a tagged tuple or a struct at
the `WorkflowYaml` boundary. It touches every caller of the shared
reader (WH and RE rules), so it is better as its own change than folded
into this fix.

---

---
_Generated by [Claude
Code](https://claude.ai/code/session_01CYR5bf5FBLS7cXQK72aVNM)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
@hyperpolymath
hyperpolymath enabled auto-merge (squash) November 9, 2026 22:45
@chatgpt-codex-connector

Copy link
Copy Markdown

This security review couldn’t start because the paying account has exhausted its Codex allowance and has insufficient available credits. The payer can check their balance and add credits in the Codex usage dashboard, then try again. If you do not manage the paying account, contact this repository's admins.

@sonarqubecloud

sonarqubecloud Bot commented Nov 9, 2026

Copy link
Copy Markdown

@hyperpolymath
hyperpolymath merged commit fb71b0d into main Nov 9, 2026
87 checks passed
@hyperpolymath
hyperpolymath deleted the claude/eager-wright-zwwbe2-workflow-yaml-docs branch November 9, 2026 22:46

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

The workflow's lines as `{line, line_number}`, without comment lines, and
with YAML aliases expanded in place (each expanded line carries the alias's
line number). Physical line numbers are kept, so a finding never points at

P2 Badge Scope the alias guarantee in active_lines/1

When a workflow contains an alias under on:, permissions:, or another section outside top-level jobs:/env:, this function returns that alias unchanged because expand_aliases/1 only expands those two sections. The follow-up scoped the separate expand_aliases/1 documentation, but this public wrapper still promises aliases are expanded without that qualification, so callers can rely on behavior it does not provide; give this contract the same section restriction.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +77 to +80
as GitHub does, within what jobs read: the top-level `jobs:` and `env:`
sections. There each `*name` is replaced in place by the node anchored
`&name` (the nearest one above, wherever it is defined), a `<<:` merge adds
the anchored entries the mapping does not set itself, and every line an

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Document the special handling of flow aliases

When an alias appears inside a flow collection, the implementation takes the value == nil branch and returns the original alias-containing line followed by copied anchor lines rather than replacing *name in place. The new public contract therefore misdescribes the returned representation for inputs such as env: [*common]; either document this append-only flow behavior, as the existing internal comment does, or actually replace the alias token.

Useful? React with 👍 / 👎.

Comment on lines +79 to +80
`&name` (the nearest one above, wherever it is defined), a `<<:` merge adds
the anchored entries the mapping does not set itself, and every line an

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove the claim that GitHub expands merge keys

When callers pass <<: *defaults, this reader expands the merge even though GitHub Actions rejects YAML merge keys: an Actions runner collaborator explicitly confirmed they are unsupported, while GitHub's documentation only describes whole-node aliases (runner issue, GitHub documentation). Describing the merge behavior as what GitHub does can cause consumers to treat an invalid workflow as executable; document it as a reader extension or stop expanding merge keys.

Useful? React with 👍 / 👎.

Comment on lines +452 to +455
@doc """
A line without its YAML comment: a `#` at the start or after whitespace,
outside a quoted scalar. A quote opens a scalar only where one can start
(after whitespace or a flow indicator), so the apostrophe in `it's` does not.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle quotes after flow-mapping colons

For compact JSON-style YAML such as {"k":"x # y"}, the value's opening quote follows :, but the implementation only recognizes quotes after whitespace, {, [, or ,. It consequently treats the # inside the quoted value as a comment and returns the truncated string {"k":"x, contrary to this newly public contract; recognize : as a valid quoted-scalar start or document that compact flow mappings are unsupported.

Useful? React with 👍 / 👎.

Comment on lines +537 to +541
Read a workflow for the line- and indent-based rules. A KYAML workflow
(YAML-POLICY Y-3: the estate's base form, `yq -o kyaml`) is rewritten to
the equivalent block layout one line for one line, so physical line
numbers in findings still point at the source. Block YAML is returned
byte-identical.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve quoted scalar semantics during KYAML conversion

When KYAML contains a string such as FLAG: "true",, unquote_scalar/1 emits FLAG: true, changing the value from a string to a boolean; similarly, SCRIPT: "echo # literal", becomes SCRIPT: echo # literal, exposing part of the value as a YAML comment. The result is therefore not an equivalent block layout as the public contract promises, and downstream line-based rules can inspect different content from GitHub; retain quotes unless removing them is YAML-semantics-preserving.

Useful? React with 👍 / 👎.

Comment on lines +83 to +85
Anchors and aliases count only where a YAML node starts, never inside
quoted scalars or block-scalar text. Expansion stops
after #{@alias_budget} copied lines, so hostile input costs bounded work.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce the documented copied-line budget

When the first alias references an anchor containing more than 5,000 block lines, anchored_block/3 subtracts the entire block length before invoking the private expander, but that expander still returns every line when its budget is non-positive. Thus a single alias can copy an arbitrarily large block despite this contract promising expansion stops after 5,000 copied lines; cap the copied block itself rather than only suppressing further nested aliases.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants