-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat(observability-map): static observability scorer for webapp route entry points #4455
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
1stvamp
wants to merge
105
commits into
main
Choose a base branch
from
feat/observability-map
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+15,957
−0
Open
Changes from all commits
Commits
Show all changes
105 commits
Select commit
Hold shift + click to select a range
ac13e08
feat(observability-map): package scaffold and route scanner
1stvamp 4fd2020
fix(observability-map): find every route entry point and scope body a…
1stvamp fa01e3e
feat(observability-map): remix route adapter
1stvamp 944f4cb
feat(observability-map): import and callee based sensitivity classifi…
1stvamp 68e0988
fix(observability-map): resolve one-hop helpers and narrow the scanne…
1stvamp 6ab9118
feat(observability-map): triviality rule and suppression comments
1stvamp 75eaaa8
feat(observability-map): the four coverage checks
1stvamp 6a07d6d
feat(observability-map): record catch clause and callee evidence on e…
1stvamp 751ea1b
feat(observability-map): rework two checks on the enriched scanner
1stvamp 3b19436
feat(observability-map): flag entry points whose catches guard one op…
1stvamp 3cb31cc
fix(observability-map): stop request-context excusing routes that log…
1stvamp ed52e17
feat(observability-map): add scoring and aggregation with an audit ga…
1stvamp f3b582f
feat(observability-map): terminal and json reports
1stvamp 7183976
fix(observability-map): stop auth-boundary accusing routes it cannot …
1stvamp a8413e1
feat(observability-map): record catch evidence per clause
1stvamp 093d447
fix(observability-map): judge catch clauses one at a time
1stvamp 1dbb49a
feat(observability-map): cli, single entry inspection and integration…
1stvamp ca8b916
fix(observability-map): stop guardsParse and branches firing on looka…
1stvamp e4eaa7c
chore(observability-map): declare vitest and scope the build
1stvamp 8232d11
fix(observability-map): stop paying routes for having no error handling
1stvamp bacda64
fix(observability-map): stop a suppression buying a better score
1stvamp 8631151
fix(observability-map): break the circularity in sensitivity
1stvamp fe62964
fix(observability-map): make the report and the CLI say what they mean
1stvamp 2f4823a
test(observability-map): cover the comment-only suppression rule
1stvamp ec5922a
fix(observability-map): report the request-context gap as a figure, n…
1stvamp 99e14ca
docs(observability-map): explain what the number means
1stvamp 3c74869
fix(observability-map): make the package typecheck and build again
1stvamp 655b3ca
fix(observability-map): stop paying for a catch that only rethrows
1stvamp 7754cac
fix(observability-map): name the suppression directive honestly, and …
1stvamp 2cb36aa
chore(observability-map): remove dead scanner surface
1stvamp 751af3f
fix(observability-map): stop crediting an if or switch that never loo…
1stvamp 11c5908
docs(observability-map): explain triviality's exclusion and the gamin…
1stvamp b897141
docs(observability-map): refresh the collapse figures and say audit-t…
1stvamp dd3c2f6
fix(observability-map): remove the last dead scanner field and pin th…
1stvamp 1df57d4
feat(observability-map): add --routes flag and a PR-comment renderer …
1stvamp 902c013
ci(observability-map): scan head vs merge base and upsert a sticky PR…
1stvamp df0333e
fix(observability-map): stop audit-trail leaking into the PR-comment …
1stvamp 960a568
fix(observability-map): make the CONTEXT line true in both renderers
1stvamp 08f6be5
fix(observability-map): derive measured from pre-suppression applicab…
1stvamp e88f04d
fix(observability-map): read suppression comments from real comment r…
1stvamp b3200c1
fix(observability-map): require a genuine read of the catch binding
1stvamp 46c5ad0
fix(observability-map): stop a dead throw counting as a rethrow
1stvamp 433ebec
fix(observability-map): require request-context to name a tenant, at …
1stvamp 2061d99
fix(observability-map): compare a parse guard against its own body, n…
1stvamp 9e3b21a
fix(observability-map): stop attributing an inline callback's catch t…
1stvamp 4704228
fix(observability-map): root the callback-boundary rule for catch att…
1stvamp 255accb
fix(observability-map): read suppression comments off the parsed sour…
1stvamp fc73a2e
fix(observability-map): make every published figure pre-suppression
1stvamp 9e30033
fix(observability-map): shadow-check a binding from any enclosing sco…
1stvamp e2e9ab3
fix(observability-map): drop the parse-guard ratio for an absolute, u…
1stvamp 4e4dd81
fix(observability-map): tighten the minors (level set, bare env, a de…
1stvamp 28c034d
fix(observability-map): read a directive off a comment, not off JSX text
1stvamp 68b8380
fix(observability-map): judge a catch on its own path, and stop excus…
1stvamp fb8ca4d
test(observability-map): make the score-cannot-rise property a corpus
1stvamp eaafa3d
test(observability-map): check that a docstring names a test that exists
1stvamp 3a95d7e
test(observability-map): test the JSX suppression fix, and name a tes…
1stvamp 07dc007
fix(observability-map): refuse three ways of adding error handling th…
1stvamp d919ee2
test(observability-map): add the additive half of the corpus
1stvamp a11b661
fix(observability-map): cut a dead error test at the right moment
1stvamp 3e1ee43
docs(observability-map): say what the unreachable-catch rule actually…
1stvamp b2e030b
fix(observability-map): read parse diagnostics through public typescr…
1stvamp d0d50f8
fix(observability-map): report a suppression that names no check inst…
1stvamp e0860dd
test(observability-map): cover the unknown-suppression line, and guar…
1stvamp 3241352
fix(observability-map): render measured state in the pr comment, and …
1stvamp 5ed9544
ci(observability-map): stop the head scan reddening a pull request, a…
1stvamp 4d39fb4
feat(observability-map): add --out, honour --json for one route, and …
1stvamp 6c54ec7
test(observability-map): give the cli tests their own routes tree, an…
1stvamp 7273d12
fix(observability-map): mark a suppression-caused row, and post when …
1stvamp b0d6dc0
ci(observability-map): make a failed comment lookup mean one thing, a…
1stvamp bccaf9f
ci: revert the observability-map widening of the internal filter
1stvamp 12f661d
ci(observability-map): gate the package tests through pr_checks, not …
1stvamp 0645447
fix(observability-map): look at the access-control surface, and stop …
1stvamp cd02789
test(observability-map): add the two guard-laundering shapes to the c…
1stvamp e755639
docs(observability-map): rewrite the README from the numbers the tool…
1stvamp d441ca9
fix(observability-map): make auth-scope ask for the tenant floor, and…
1stvamp a65ff6b
test(observability-map): budget the real-tree scan instead of leaving…
1stvamp e250040
test(observability-map): colocate tests with their sources
1stvamp 9b26552
fix(observability-map): repoint imports and guards to the colocated t…
1stvamp a7e6a17
fix(observability-map): point the corpus job at the moved file and ow…
1stvamp 3635f2f
fix(observability-map): make audit-trail true, and stop one dead stat…
1stvamp 7b207e2
docs(observability-map): disclose the free point auth-scope carries, …
1stvamp 87b7382
fix(observability-map): trim trailing underscores without a backtrack…
1stvamp 77179f1
fix(observability-map): make the corpus harness cover what it claims
1stvamp db8e1bd
fix(observability-map): print the unknown-suppression warning once
1stvamp cff46a7
docs(observability-map): correct a mutation's what text and tag a REA…
1stvamp 951d430
fix(observability-map): credit a thrown ternary that classifies the e…
1stvamp 023bc03
test(observability-map): stop the directive-prologue test passing on …
1stvamp 87e0822
fix(observability-map): stop a switch's break cutting the statements …
1stvamp c53cbfd
docs(observability-map): note that auth-scope reads only the builder-…
1stvamp 467a7fd
fix(observability-map): stop a dead statement blinding the catch walk
1stvamp 2cb4233
fix(observability-map): enter the walk exactly where execution is gua…
1stvamp 78e6ad5
fix(observability-map): judge refused callback catches on their evidence
1stvamp 184f441
test(observability-map): assert the mirror direction of the corpus pr…
1stvamp fac948a
fix(observability-map): refuse catch evidence from a try a finally ca…
1stvamp ea6d6ff
fix(observability-map): block the refused-swallow accusation on any o…
1stvamp 61fe11d
fix(observability-map): refuse a log call as evidence of caller scoping
1stvamp 32c301d
ci(observability-map): stop the nightly corpus depending on the paths…
1stvamp f06e723
chore(observability-map): declare the ES2020 lib the tests already need
1stvamp 5ad6026
fix(observability-map): keep the test task out of the turbo cache
1stvamp 00b78cb
ci(observability-map): write scan reports with --out instead of captu…
1stvamp 26c4059
ci(observability-map): widen the obsmap filter to what the suite reads
1stvamp 4ea20b0
test(observability-map): size the real-tree timeouts for shard conten…
1stvamp 30fdb02
fix(observability-map): attribute auth-boundary guards per export
1stvamp 4f80d58
fix(observability-map): widen the corpus helper to every export form
1stvamp 2de22e4
refactor(observability-map): share or pin the rest of the duplicated …
1stvamp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,310 @@ | ||
| name: 🗺️ Observability Map | ||
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, reopened] | ||
| paths: | ||
| - "apps/webapp/app/routes/**" | ||
| - "internal-packages/observability-map/**" | ||
| # The corpus job below is gated to this package's own paths, so a scheduled run is what still | ||
| # scans the tree as it drifts. Nightly rather than per route pull request: a new route can make a | ||
| # known laundering shape start paying, but that is a property of the tree accumulating, not of any | ||
| # one pull request, and it does not need catching within five minutes of the merge. | ||
| schedule: | ||
| - cron: "0 3 * * *" | ||
| workflow_dispatch: | ||
|
|
||
| concurrency: | ||
| group: observability-map-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| # The workflow's paths filter is the union of what the two jobs below want, because GitHub | ||
| # evaluates it once per workflow. This narrows it again for the corpus job alone. | ||
| changes: | ||
| name: 🔍 Which paths moved | ||
| # Only the pull request path reads this job's output. On a schedule the action has no base to | ||
| # diff, warns that `before` is missing and reports the files in the last commit on main, which | ||
| # nothing then consults. Skipping it there keeps the nightly off a job it does not need. | ||
| if: github.event_name == 'pull_request' | ||
| runs-on: warp-ubuntu-latest-x64-2x | ||
| outputs: | ||
| package: ${{ steps.filter.outputs.package }} | ||
| steps: | ||
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| fetch-depth: 1 | ||
| persist-credentials: false | ||
| - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 | ||
| id: filter | ||
| with: | ||
| filters: | | ||
| package: | ||
| - 'internal-packages/observability-map/**' | ||
| - '.github/workflows/observability-map.yml' | ||
|
|
||
| # The tree-scale mutation corpus: every known laundering shape applied to the whole route tree, | ||
| # asserting the score does not rise. Roughly four and a half minutes for 45 entries, which is why | ||
| # it is gated out of the package's default `pnpm test` and run here instead. Unlike the report | ||
| # job below it has no token to lose, so it runs for fork PRs too, and unlike the report job it is | ||
| # allowed to fail the build. | ||
| # | ||
| # Gated to this package's own paths rather than running on every route pull request. What the | ||
| # corpus measures is the TOOL's resistance to laundering, and only an edit to the tool can weaken | ||
| # that, so a routes-only change was paying four and a half minutes of a 4x runner for a result | ||
| # that could not differ from the last one. It was also the worst kind of job to spend that on: a | ||
| # red x that fires on a large share of webapp pull requests, is allowed to fail, and gates | ||
| # nothing, which is the shape people learn to scroll past. | ||
| # | ||
| # What this gives up is real and small. A route landing a shape no corpus entry has seen can make | ||
| # a known laundering mutation start paying, and that is now caught by the nightly rather than by | ||
| # the pull request that caused it. Tree drift accrues over months, so a day is the right | ||
| # granularity for it; the tool's own regressions, which are the ones a single commit can cause, | ||
| # still gate per pull request. | ||
| mutation-corpus: | ||
| name: 🧬 Mutation corpus | ||
| needs: changes | ||
| # `!cancelled()` is here for the nightly, not for tidiness. `needs` carries an implicit | ||
| # success() on the job it names, and that implicit test outranks the `||` below: with a plain | ||
| # condition, a `changes` job that failed or was skipped skips this one, so the nightly would | ||
| # stop scanning for tree drift and report nothing about having stopped. A status-check function | ||
| # in the `if` is what drops the implicit success(), so the event test below decides alone. | ||
| # `!cancelled()` rather than `always()` because `cancel-in-progress` above is a real path and a | ||
| # superseded run should not finish this job. | ||
| # | ||
| # Pull request behaviour is deliberately unchanged: on a PR a failed `changes` leaves | ||
| # `needs.changes.outputs.package` empty, so the corpus still skips. The nightly is the backstop | ||
| # for that, which is the same trade the paths gate already makes for routes-only pull requests. | ||
| if: >- | ||
| !cancelled() && | ||
| (github.event_name != 'pull_request' || needs.changes.outputs.package == 'true') | ||
| runs-on: warp-ubuntu-latest-x64-4x | ||
| steps: | ||
| - name: ⬇️ Checkout repo | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| fetch-depth: 1 | ||
| persist-credentials: false | ||
|
|
||
| - name: ⎔ Setup pnpm | ||
| uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 | ||
| with: | ||
| version: 10.33.2 | ||
|
|
||
| - name: ⎔ Setup node | ||
| uses: WarpBuilds/setup-node@bc639b444d583175926b588962199c247d23e8d3 # v6 | ||
| with: | ||
| node-version: 24.18.0 | ||
| cache: "pnpm" | ||
|
|
||
| - name: 📥 Download deps | ||
| run: pnpm install --frozen-lockfile | ||
|
|
||
| - name: 🧬 Run the corpus | ||
| env: | ||
| OBS_MAP_MUTATION_CORPUS: "1" | ||
| run: | | ||
| pnpm --filter @internal/observability-map exec vitest run \ | ||
| src/mutationCorpus.test.ts --disable-console-intercept | ||
|
|
||
| # The package's own tests are NOT run here. They gate through pr_checks.yml, which is the only | ||
| # workflow the all-checks aggregate can see, so a job in this file would report a result nobody | ||
| # is required to wait for. See unit-tests-observability-map.yml and the obsmap filter. | ||
| report: | ||
| runs-on: warp-ubuntu-latest-x64-4x | ||
| # Only this job comments, so only this job gets the write. | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| # Fork PRs get a read-only token, so the comment cannot post. Skipping the job beats a red x. | ||
| # The event test is what keeps this job off the nightly, which has no pull request to comment on | ||
| # and only exists for the corpus job above. | ||
| if: >- | ||
| github.event_name == 'pull_request' && | ||
| github.event.pull_request.head.repo.full_name == github.repository | ||
| steps: | ||
| - name: ⬇️ Checkout repo | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| - name: ⎔ Setup pnpm | ||
| uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 | ||
| with: | ||
| version: 10.33.2 | ||
|
|
||
| - name: ⎔ Setup node | ||
| uses: WarpBuilds/setup-node@bc639b444d583175926b588962199c247d23e8d3 # v6 | ||
| with: | ||
| node-version: 24.18.0 | ||
| cache: "pnpm" | ||
|
|
||
| - name: 📥 Download deps | ||
| run: pnpm install --frozen-lockfile | ||
|
|
||
| # Guarded rather than allowed to fail: this job must never block a pull request. The failure | ||
| # is not swallowed either, the render step below turns a missing head report into a comment | ||
| # saying so, because a swallowed failure with no comment is the outcome nobody wants. | ||
| # | ||
| # `--out` rather than a stdout redirect, so nothing a tool decides to print can end up inside | ||
| # the document `prCommentCli` parses. `pnpm --filter` takes its recursive path and some | ||
| # versions announce `Scope: N of M workspace projects` on the way; that line landing in | ||
| # head.json would fail the parse and degrade every run to the stale-report comment, which is | ||
| # a permanent quiet failure rather than a loud one. It does not reproduce on the 10.33.2 | ||
| # pinned above, so this closes the class rather than a reproduction: the file is written by | ||
| # the process that owns it and stdout is left to be log output. Held by | ||
| # `it("let the scanner write its own report rather than capturing stdout")` in | ||
| # `internal-packages/observability-map/src/integration.test.ts`. | ||
| # | ||
| # `-s` keeps the partial dance honest now the redirect no longer creates the file: a scanner | ||
| # that exits 0 without writing takes the else branch and the stale-report comment, instead of | ||
| # failing the `mv` and turning the job red. | ||
| - name: 🔎 Scan head | ||
| run: | | ||
| if pnpm --filter @internal/observability-map exec tsx src/cli.ts \ | ||
| --out=/tmp/head.json.partial && [ -s /tmp/head.json.partial ]; then | ||
| mv /tmp/head.json.partial /tmp/head.json | ||
| else | ||
| rm -f /tmp/head.json /tmp/head.json.partial | ||
| echo "head scan failed; the comment will say the report is stale for this run" >&2 | ||
| fi | ||
|
|
||
| # base.sha, not a merge base, and two reviewers have now read that as a bug. The checkout | ||
| # above is the default for a pull_request event, so the working tree is GitHub's test merge | ||
| # commit, whose parents are base.sha and the PR head. The head tree therefore already contains | ||
| # the base branch up to base.sha, and diffing it against base.sha is what isolates this pull | ||
| # request's own work. A merge base would leave the intervening base-branch commits in the head | ||
| # tree and out of the base tree, and blame the pull request for all of them. | ||
| - name: 🔎 Scan base with the head's scanner | ||
| run: | | ||
| if git worktree add /tmp/base-tree ${{ github.event.pull_request.base.sha }} \ | ||
| && pnpm --filter @internal/observability-map exec tsx src/cli.ts \ | ||
| --routes=/tmp/base-tree/apps/webapp/app/routes --out=/tmp/base.json \ | ||
| && [ -s /tmp/base.json ]; then | ||
| : | ||
| else | ||
| echo "-" > /tmp/base.json || true | ||
| echo "base scan failed or the worktree could not be added; falling back to no base" >&2 | ||
| fi | ||
|
1stvamp marked this conversation as resolved.
1stvamp marked this conversation as resolved.
|
||
|
|
||
| # Looked up before the render step because the render decision needs it: with no delta to | ||
| # report, a pull request that already has a comment gets a resolved state rather than being | ||
| # left with findings that no longer exist, and one that does not gets nothing at all. The | ||
| # upsert step reuses the id rather than asking twice. | ||
| # | ||
| # On a failure that outlasts the retries, both steps below do nothing. Guessing is worse than | ||
| # silence here: this step is the only thing that knows which comment to PATCH, so a guess of | ||
| # "a comment exists" still reaches an upsert with no id to patch, which POSTs. That either | ||
| # adds a second marker comment beside the stale one, or says "the findings an earlier push | ||
| # reported are gone" on a pull request that never had findings. Worst case now is no comment | ||
| # this run, which the next push fixes. | ||
| - name: 🔍 Look for a comment from an earlier push | ||
| continue-on-error: true | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| run: | | ||
| rm -f /tmp/existing-comment-id /tmp/comment-lookup-failed | ||
| found="" | ||
| ok="" | ||
| for attempt in 1 2 3; do | ||
| if found=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" --paginate \ | ||
| --jq '[.[] | select(.body | startswith("<!-- observability-map-report -->"))][0].id // empty'); then | ||
| ok=1 | ||
| break | ||
| fi | ||
| echo "comment lookup attempt ${attempt} failed" >&2 | ||
| sleep $((attempt * 5)) | ||
| done | ||
|
|
||
| if [ -z "$ok" ]; then | ||
| touch /tmp/comment-lookup-failed | ||
| echo "comment lookup failed after 3 attempts; this run posts nothing" >&2 | ||
| exit 0 | ||
| fi | ||
|
|
||
| # --paginate runs the jq once per page, so a marker comment on more than one page yields | ||
| # one id per page. Unhandled, that puts a newline in the PATCH url and the step dies under | ||
| # continue-on-error. The oldest wins: it is the one the upsert has been updating. | ||
| count=$(printf '%s\n' "$found" | grep -c '[0-9]' || true) | ||
| if [ "$count" -gt 1 ]; then | ||
| echo "warning: ${count} marker comments on this pull request; updating the oldest" >&2 | ||
| fi | ||
| printf '%s\n' "$found" | awk 'NF { print $1; exit }' > /tmp/existing-comment-id | ||
|
|
||
| # continue-on-error for the same reason as the scan: a rendering bug must not turn the job | ||
| # red. An empty /tmp/comment.md means there is nothing to post, which is a decision | ||
| # prCommentCli makes, not this shell. | ||
| - name: 📝 Render comment | ||
| continue-on-error: true | ||
| run: | | ||
| rm -f /tmp/comment.md | ||
| render() { pnpm --filter @internal/observability-map exec tsx src/report/prCommentCli.ts "$@"; } | ||
|
|
||
| # Every write goes through this, so a renderer that exits non-zero never leaves a 0-byte | ||
| # comment.md for the upsert to skip in silence. | ||
| emit() { | ||
| if render "$@" > /tmp/comment.md.partial; then | ||
| mv /tmp/comment.md.partial /tmp/comment.md | ||
| return 0 | ||
| fi | ||
| rm -f /tmp/comment.md.partial | ||
| return 1 | ||
| } | ||
|
|
||
| if [ -f /tmp/comment-lookup-failed ]; then | ||
| echo "the comment lookup failed, so this run posts nothing" >&2 | ||
| exit 0 | ||
| fi | ||
|
|
||
| if [ ! -s /tmp/head.json ]; then | ||
| emit --scan-failed || echo "could not render the stale-report comment either" >&2 | ||
| exit 0 | ||
| fi | ||
|
|
||
| base=/tmp/base.json | ||
| if [ ! -s /tmp/base.json ] || [ "$(cat /tmp/base.json)" = "-" ]; then | ||
| base="-" | ||
| fi | ||
|
|
||
| flags=() | ||
| if [ -s /tmp/existing-comment-id ]; then | ||
| flags=(--existing-comment) | ||
| fi | ||
|
|
||
| if ! emit /tmp/head.json "$base" "${flags[@]}"; then | ||
| echo "render failed; falling back to the stale-report comment" >&2 | ||
| emit --scan-failed || echo "could not render the stale-report comment either" >&2 | ||
| fi | ||
|
|
||
| # continue-on-error for the same reason: a transient gh api failure (rate limit, network) | ||
| # must not fail the job either. Worst case, the PR gets no comment this run. | ||
| - name: 💬 Upsert PR comment | ||
| continue-on-error: true | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| run: | | ||
| # The same sentinel the render step reads, so the two cannot disagree about what a failed | ||
| # lookup means. Without it this step reads a missing id as "no comment exists" and POSTs. | ||
| if [ -f /tmp/comment-lookup-failed ]; then | ||
| echo "the comment lookup failed, so this run posts nothing" | ||
| exit 0 | ||
| fi | ||
| if [ ! -s /tmp/comment.md ]; then | ||
| echo "nothing to post: this pull request does not move the report" | ||
| exit 0 | ||
| fi | ||
| existing="" | ||
| if [ -f /tmp/existing-comment-id ]; then | ||
| existing=$(cat /tmp/existing-comment-id) | ||
| fi | ||
| if [ -n "$existing" ]; then | ||
| gh api -X PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${existing}" -F body=@/tmp/comment.md | ||
| else | ||
| gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" -F body=@/tmp/comment.md | ||
| fi | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.