Proof of concept GitHub Actions workflow that converts top-level PR comments into file-attached review comments for better thread organization. It moves comments to a dedicated placeholder file (subject_type: "file"
) and deletes the original, enabling structured discussions without external tools. 🚀
Originally explored in this discussion and re-shared in this discussion.
- Detects new top-level PR comments
- Moves them to a placeholder file as review comments (
subject_type: "file"
) - Deletes the original comment to keep discussions clean
- Ensures the placeholder file exists before posting
- Uses an
ADMIN_TOKEN
for proper API permissions
This approach enables structured PR conversations without third-party tools like Pullpo. 🚀
name: Convert PR Top-Level Comment to File Comment
on:
issue_comment:
types: [created]
env:
COMMITTER_NAME: "github-actions"
COMMITTER_EMAIL: "github-actions@github.com"
jobs:
convert-comment:
runs-on: ubuntu-latest
if: ${{ github.event.issue.pull_request != null }}
env:
PLACEHOLDER_FILE: "thread-comments/pr_${{ github.event.issue.number }}_threads.txt"
steps:
- name: Get PR details
id: pr_details
uses: actions/github-script@v6
with:
script: |
const pull_number = context.payload.issue.number;
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pull_number
});
core.setOutput("head_ref", pr.head.ref);
core.setOutput("head_sha", pr.head.sha);
token: ${{ secrets.ADMIN_TOKEN }}
- name: Checkout PR branch
uses: actions/checkout@v4
with:
ref: ${{ steps.pr_details.outputs.head_ref }}
token: ${{ secrets.ADMIN_TOKEN }}
fetch-depth: 0
- name: Ensure placeholder file exists
id: ensure_placeholder
run: |
if [ -f "${PLACEHOLDER_FILE}" ]; then
echo "Placeholder file exists. Skipping creation."
else
mkdir -p "$(dirname "${PLACEHOLDER_FILE}")"
cat <<EOF > "${PLACEHOLDER_FILE}"
# Threaded Discussion Placeholder
This file is used to anchor threaded comments for this pull request.
Do not modify this file manually.
<!-- Auto-generated by GitHub Action -->
EOF
git config user.name "${COMMITTER_NAME}"
git config user.email "${COMMITTER_EMAIL}"
git add "${PLACEHOLDER_FILE}"
git commit -m "Add placeholder file for threaded discussions"
git push origin ${{ steps.pr_details.outputs.head_ref }}
fi
- name: Create file-level review comment on placeholder file
id: create_review_comment
uses: actions/github-script@v6
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const pull_number = context.payload.issue.number;
const commit_id = "${{ steps.pr_details.outputs.head_sha }}";
const placeholderPath = "${PLACEHOLDER_FILE}";
const body = `**Threaded comment by @${context.payload.comment.user.login}:**\n\n${context.payload.comment.body}`;
// Using subject_type "file" creates a file-level review comment.
const { data: reviewComment } = await github.rest.pulls.createReviewComment({
owner,
repo,
pull_number,
commit_id,
path: placeholderPath,
body: body,
subject_type: "file"
});
core.info("Created review comment with ID: " + reviewComment.id);
token: ${{ secrets.ADMIN_TOKEN }}
- name: Delete original top-level comment
uses: actions/github-script@v6
with:
script: |
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id
});
core.info("Deleted original top-level comment");
token: ${{ secrets.ADMIN_TOKEN }}
The following is the original discussion comment, for context/reference:
Looking at the setup docs for this, it seems the threading feature is enabled by a GitHub action that they include inline on the docs page
.github/workflows/pullpo.yaml
## .github/workflows/pullpo.yaml name: Pullpo PR Threads on: pull_request: types: [opened] jobs: pullpo_threads: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.ref }} fetch-depth: 0 - name: Create Pullpo thread file with PR SHA run: | mkdir -p 一pullpo一 rm -rf 一pullpo一/* echo "# Pullpo Threaded Conversations on GitHub ## What is the 一pullpo一 directory I see on my project? The `一pullpo一` directory allows your team to respond to comments that do not reference code in a dedicated thread, instead of having to \"quote reply\" which makes following a conversation more complex than it needs to be. **DO NOT REMOVE OR .GITIGNORE THIS DIRECTORY OR THE AUTO-GENERATED FILES IT CONTAINS**. Doing so will prevent you, your reviewers and ohter contributors from being able to have threaded messages in GitHub. ## What files does it contain? The files inside this directory are created by a workflow (which you can probably find in `.github/workflows`) that is executed when a new pull request is open in this repository. This file is added in a new commit to your pull request. The **Pullpo GitHub App** transforms your normal comments into threaded comments by commenting on this uniquely named file. ## Will this directory keep getting bigger and bigger? No, the workflow removes all previous files in the directory. Since files have unique names within a repository, this avoids merge conflicts and also prevents an infinetly increasing file list. If the `一pullpo一` directory contains more than one file, it is likely because multiple pull requests were open at the same time at one point. ## How will this appear on my Pull Request `Conversation` or `Files Changed` tabs? The `Conversation` tab will have the new threaded comments. Ideally you'll have [logged into Pullpo](https://pullpo.io/login/github) with your GitHub account so the Pullpo App will be able to replace the new comments as authored by you. Otherwise they will show up as an action that the bot made and you'll be tagged in the new threaded comment. As for the `Files Changed` tab, the `一pullpo一` directory has been named by prefixing a special character that sorts it to the bottom of the page, so all comment threads will sit at the bottom and won't disturb anyone trying to read your changes. ## So, what now...? Keep contributing as you're used to; your pull requests will receive an additional commit that enables threaded comments. Other than that everything stays as usual. " > 一pullpo一/README.md touch 一pullpo一/pr_${{ github.event.pull_request.number }}_threads.txt git config user.name github-actions git config user.email github-actions@github.com git add . git commit -m "add-pullpo-pr-threads" git pushSummarizing that GitHub action with ChatGPT 4o:
This GitHub Action is designed to integrate Pullpo's threaded conversation feature into pull requests by automatically creating a special directory and files when a new PR is opened. Here's a high-level breakdown of what it does:
Triggered on New PRs
- The action runs when a new pull request is opened.
Checks Out the PR Branch
- It checks out the PR branch with full history (
fetch-depth: 0
) to allow proper commit history handling.Creates a Special Directory (
一pullpo一
)
- The action ensures the existence of a directory named
一pullpo一
.- Any previous files in this directory are removed to keep it clean.
Adds a README File Explaining Pullpo
- It creates a
README.md
inside the directory, explaining that Pullpo enables threaded discussions for PR comments.Generates a Unique Thread File
- A placeholder file is created (
pr_<PR_NUMBER>_threads.txt
), which will be used by Pullpo to track threaded conversations.Commits & Pushes the Changes
- The workflow sets up Git with
github-actions
as the user.- The new directory and files are committed and pushed to the PR branch.
This automation ensures that every new pull request includes the necessary files for Pullpo to enable threaded discussions on GitHub, allowing more structured conversations instead of relying on manual quoting.
Looking at the demo PR's 'How does it work?' section:
When a pull request is opened, it creates a special folder called 一pullpo一 if it doesn't already exist. We named it this way so that it always appears at the end of the files changed tab. Inside this folder, a simple text file is created to track discussions related to this specific pull request. Based on how it works, these files will never create merge conflicts.
Then when someone creates an issue comment on GitHub (or in a PR Slack channel), the Pullpo app is going to take that message and transform it into a file comment message referencing this newly created file.
Basically, this action sets up a spot for threaded conversations linked to pull requests. It helps the Pullpo app to organize comments more neatly.
Based on the above:
This requires external tool subscription? and unclear if it's free or not.
@romanr From a quick skim, the GitHub action seems to do very little by itself, only creating a the placeholder file that the main app uses. It seems the bulk of this functionality will be handled by the Pullpo app itself; and therefore based on what I can determine from a skim of their pricing page, sounds like it will require a paid subscription.
Based on the description of how this feature works, at a high level, it seems like the Pullpo app will just notice when someone has made a comment on the main PR, convert that to a new file comment on the
一pullpo一/pr_<PR_NUMBER>_threads.txt
placeholder file (creating the comment as the user if it has permission to; and falling back to posting as the app bot if it doesn't have permissions to post as that user), and then deletes the old top-level comment.Based on that functionality.. you could probably implement something similar purely within a GitHub action alone if you really wanted to... though personally, I don't really like the idea of littering my PR's with an unrelated file just to serve as a placeholder for where to create these new comment threads on.
I haven't deeply reviewed/tested the following GitHub Action, but here's a quick proof-of-concept I knocked up for this using ChatGPT
o3-mini-high
. It can't post the new comment as the user themselves.. but aside from that I think it pretty much implements the same basic workflow/etc:
- Event: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#issue_comment
- Payload: https://docs.github.com/en/webhooks/webhook-events-and-payloads#issue_comment
- Creating a comment: https://docs.github.com/en/rest/pulls/comments#create-a-review-comment-for-a-pull-request
name: Convert PR Top-Level Comment to File Comment on: issue_comment: types: [created] env: COMMITTER_NAME: "github-actions" COMMITTER_EMAIL: "github-actions@github.com" jobs: convert-comment: runs-on: ubuntu-latest if: ${{ github.event.issue.pull_request != null }} env: PLACEHOLDER_FILE: "thread-comments/pr_${{ github.event.issue.number }}_threads.txt" steps: - name: Get PR details id: pr_details uses: actions/github-script@v6 with: script: | const pull_number = context.payload.issue.number; const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: pull_number }); core.setOutput("head_ref", pr.head.ref); core.setOutput("head_sha", pr.head.sha); token: ${{ secrets.ADMIN_TOKEN }} - name: Checkout PR branch uses: actions/checkout@v4 with: ref: ${{ steps.pr_details.outputs.head_ref }} token: ${{ secrets.ADMIN_TOKEN }} fetch-depth: 0 - name: Ensure placeholder file exists id: ensure_placeholder run: | if [ -f "${PLACEHOLDER_FILE}" ]; then echo "Placeholder file exists. Skipping creation." else mkdir -p "$(dirname "${PLACEHOLDER_FILE}")" cat <<EOF > "${PLACEHOLDER_FILE}" # Threaded Discussion Placeholder This file is used to anchor threaded comments for this pull request. Do not modify this file manually. <!-- Auto-generated by GitHub Action --> EOF git config user.name "${COMMITTER_NAME}" git config user.email "${COMMITTER_EMAIL}" git add "${PLACEHOLDER_FILE}" git commit -m "Add placeholder file for threaded discussions" git push origin ${{ steps.pr_details.outputs.head_ref }} fi - name: Create file-level review comment on placeholder file id: create_review_comment uses: actions/github-script@v6 with: script: | const owner = context.repo.owner; const repo = context.repo.repo; const pull_number = context.payload.issue.number; const commit_id = "${{ steps.pr_details.outputs.head_sha }}"; const placeholderPath = "${PLACEHOLDER_FILE}"; const body = `**Threaded comment by @${context.payload.comment.user.login}:**\n\n${context.payload.comment.body}`; // Using subject_type "file" creates a file-level review comment. const { data: reviewComment } = await github.rest.pulls.createReviewComment({ owner, repo, pull_number, commit_id, path: placeholderPath, body: body, subject_type: "file" }); core.info("Created review comment with ID: " + reviewComment.id); token: ${{ secrets.ADMIN_TOKEN }} - name: Delete original top-level comment uses: actions/github-script@v6 with: script: | await github.rest.issues.deleteComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: context.payload.comment.id }); core.info("Deleted original top-level comment"); token: ${{ secrets.ADMIN_TOKEN }}Originally posted by @0xdevalias in https://github.com/orgs/community/discussions/5633#discussioncomment-12088921