From fce9ad5731923ac099e1173d76dcc51edf63cf17 Mon Sep 17 00:00:00 2001 From: Gregg Coppen Date: Fri, 24 Oct 2025 11:29:03 -0700 Subject: [PATCH 1/5] Enable automatic workflow approval for Liatrio Labs organization members MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit updates the Claude Code and OpenCode GPT-5 Codex workflows to automatically allow workflow execution for members of the liatrio-labs GitHub organization without requiring manual approval. Changes: - Added check-org-membership job to both workflows - Checks author_association first (OWNER, MEMBER, COLLABORATOR) - Falls back to checking liatrio-labs organization membership via GitHub API - Main workflow jobs now depend on authorization check passing This ensures that: 1. Existing collaborators continue to work without changes 2. Any member of liatrio-labs organization can trigger workflows 3. Non-members and non-collaborators are still blocked 🤖 Generated with Claude Code Co-Authored-By: Claude --- .github/workflows/claude.yml | 63 +++++++++++++++++----- .github/workflows/opencode-gpt-5-codex.yml | 63 +++++++++++++++++----- 2 files changed, 100 insertions(+), 26 deletions(-) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index a7580ee..2d8150c 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -11,33 +11,70 @@ on: types: [submitted] jobs: - claude: - timeout-minutes: 10 - concurrency: - group: claude-${{ github.event_name }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} - cancel-in-progress: true + # Check if the user is a member of liatrio-labs organization + check-org-membership: + runs-on: ubuntu-latest if: | ( github.event_name == 'issue_comment' && - contains(github.event.comment.body, '@claude') && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + contains(github.event.comment.body, '@claude') ) || ( github.event_name == 'pull_request_review_comment' && - contains(github.event.comment.body, '@claude') && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + contains(github.event.comment.body, '@claude') ) || ( github.event_name == 'pull_request_review' && github.event.review.body != null && - contains(github.event.review.body, '@claude') && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association) + contains(github.event.review.body, '@claude') ) || ( github.event_name == 'issues' && ( (github.event.issue.body != null && contains(github.event.issue.body, '@claude')) || contains(github.event.issue.title, '@claude') - ) && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association) + ) ) + outputs: + is-authorized: ${{ steps.check.outputs.authorized }} + steps: + - name: Check authorization + id: check + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ACTOR="${{ github.actor }}" + + # Check if user is a repo collaborator/owner/member first + if [[ "${{ github.event_name }}" == "issue_comment" ]]; then + AUTHOR_ASSOC="${{ github.event.comment.author_association }}" + elif [[ "${{ github.event_name }}" == "pull_request_review_comment" ]]; then + AUTHOR_ASSOC="${{ github.event.comment.author_association }}" + elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then + AUTHOR_ASSOC="${{ github.event.review.author_association }}" + elif [[ "${{ github.event_name }}" == "issues" ]]; then + AUTHOR_ASSOC="${{ github.event.issue.author_association }}" + fi + + if [[ "$AUTHOR_ASSOC" == "OWNER" ]] || [[ "$AUTHOR_ASSOC" == "MEMBER" ]] || [[ "$AUTHOR_ASSOC" == "COLLABORATOR" ]]; then + echo "User is authorized via author_association: $AUTHOR_ASSOC" + echo "authorized=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Check if user is a member of liatrio-labs organization + if gh api "orgs/liatrio-labs/members/$ACTOR" --silent 2>/dev/null; then + echo "User is authorized as liatrio-labs organization member" + echo "authorized=true" >> "$GITHUB_OUTPUT" + else + echo "User is not authorized" + echo "authorized=false" >> "$GITHUB_OUTPUT" + fi + + claude: + needs: check-org-membership + if: needs.check-org-membership.outputs.is-authorized == 'true' + timeout-minutes: 10 + concurrency: + group: claude-${{ github.event_name }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} + cancel-in-progress: true runs-on: ubuntu-latest permissions: contents: read diff --git a/.github/workflows/opencode-gpt-5-codex.yml b/.github/workflows/opencode-gpt-5-codex.yml index 7066901..28f6040 100644 --- a/.github/workflows/opencode-gpt-5-codex.yml +++ b/.github/workflows/opencode-gpt-5-codex.yml @@ -11,33 +11,70 @@ on: types: [submitted] jobs: - opencode: - timeout-minutes: 30 # to accommodate Codex's ability to run for extended periods - concurrency: - group: opencode-${{ github.event_name }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} - cancel-in-progress: true + # Check if the user is a member of liatrio-labs organization + check-org-membership: + runs-on: ubuntu-latest if: | ( github.event_name == 'issue_comment' && - contains(github.event.comment.body, '/oc-codex') && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + contains(github.event.comment.body, '/oc-codex') ) || ( github.event_name == 'pull_request_review_comment' && - contains(github.event.comment.body, '/oc-codex') && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + contains(github.event.comment.body, '/oc-codex') ) || ( github.event_name == 'pull_request_review' && github.event.review.body != null && - contains(github.event.review.body, '/oc-codex') && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association) + contains(github.event.review.body, '/oc-codex') ) || ( github.event_name == 'issues' && ( (github.event.issue.body != null && contains(github.event.issue.body, '/oc-codex')) || contains(github.event.issue.title, '/oc-codex') - ) && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association) + ) ) + outputs: + is-authorized: ${{ steps.check.outputs.authorized }} + steps: + - name: Check authorization + id: check + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ACTOR="${{ github.actor }}" + + # Check if user is a repo collaborator/owner/member first + if [[ "${{ github.event_name }}" == "issue_comment" ]]; then + AUTHOR_ASSOC="${{ github.event.comment.author_association }}" + elif [[ "${{ github.event_name }}" == "pull_request_review_comment" ]]; then + AUTHOR_ASSOC="${{ github.event.comment.author_association }}" + elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then + AUTHOR_ASSOC="${{ github.event.review.author_association }}" + elif [[ "${{ github.event_name }}" == "issues" ]]; then + AUTHOR_ASSOC="${{ github.event.issue.author_association }}" + fi + + if [[ "$AUTHOR_ASSOC" == "OWNER" ]] || [[ "$AUTHOR_ASSOC" == "MEMBER" ]] || [[ "$AUTHOR_ASSOC" == "COLLABORATOR" ]]; then + echo "User is authorized via author_association: $AUTHOR_ASSOC" + echo "authorized=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Check if user is a member of liatrio-labs organization + if gh api "orgs/liatrio-labs/members/$ACTOR" --silent 2>/dev/null; then + echo "User is authorized as liatrio-labs organization member" + echo "authorized=true" >> "$GITHUB_OUTPUT" + else + echo "User is not authorized" + echo "authorized=false" >> "$GITHUB_OUTPUT" + fi + + opencode: + needs: check-org-membership + if: needs.check-org-membership.outputs.is-authorized == 'true' + timeout-minutes: 30 # to accommodate Codex's ability to run for extended periods + concurrency: + group: opencode-${{ github.event_name }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} + cancel-in-progress: true runs-on: ubuntu-latest permissions: contents: read From 215cc738720a9d221f0ee8cf951c09da64f19667 Mon Sep 17 00:00:00 2001 From: Gregg Coppen Date: Fri, 24 Oct 2025 11:49:31 -0700 Subject: [PATCH 2/5] Use ORG_MEMBER_CHECK_TOKEN for organization membership verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This addresses the CodeRabbit review feedback regarding GITHUB_TOKEN permissions. The default GITHUB_TOKEN does not have org-level "Members" read permission required to query organization membership. Changes: - Updated both workflows to use ORG_MEMBER_CHECK_TOKEN secret - This token must be a PAT or GitHub App token with read:org scope Setup Required: A repository administrator must create and add the ORG_MEMBER_CHECK_TOKEN secret with appropriate permissions. See PR description for instructions. Fixes: Critical permission issue identified in CodeRabbit review 🤖 Generated with Claude Code Co-Authored-By: Claude --- .github/workflows/claude.yml | 2 +- .github/workflows/opencode-gpt-5-codex.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 2d8150c..858542e 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -38,7 +38,7 @@ jobs: - name: Check authorization id: check env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.ORG_MEMBER_CHECK_TOKEN }} run: | ACTOR="${{ github.actor }}" diff --git a/.github/workflows/opencode-gpt-5-codex.yml b/.github/workflows/opencode-gpt-5-codex.yml index 28f6040..c87a30c 100644 --- a/.github/workflows/opencode-gpt-5-codex.yml +++ b/.github/workflows/opencode-gpt-5-codex.yml @@ -38,7 +38,7 @@ jobs: - name: Check authorization id: check env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.ORG_MEMBER_CHECK_TOKEN }} run: | ACTOR="${{ github.actor }}" From 4c5cdc9d24ffb568a38feaf3a47745192261bdfd Mon Sep 17 00:00:00 2001 From: Gregg Coppen Date: Fri, 24 Oct 2025 11:58:16 -0700 Subject: [PATCH 3/5] Refactor authorization logic into reusable composite action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This addresses the CodeRabbit refactoring suggestion to eliminate duplicated authorization logic across workflows. Changes: - Created .github/actions/check-org-membership composite action - Extracted 45+ lines of duplicated bash logic into single reusable action - Action accepts trigger-command as input parameter - Both workflows now use the same authorization logic - Reduced maintenance burden and ensured consistency Benefits: - Single source of truth for authorization logic - DRY principle compliance - Future updates apply consistently across all workflows - Easier to test and maintain The composite action: - Checks author_association first (OWNER, MEMBER, COLLABORATOR) - Falls back to organization membership verification - Returns is-authorized boolean output - Supports all event types (issue_comment, pull_request_review, etc.) Addresses: CodeRabbit refactoring suggestion 🤖 Generated with Claude Code Co-Authored-By: Claude --- .../actions/check-org-membership/action.yml | 98 +++++++++++++++++++ .github/workflows/claude.yml | 49 ++++------ .github/workflows/opencode-gpt-5-codex.yml | 49 ++++------ 3 files changed, 134 insertions(+), 62 deletions(-) create mode 100644 .github/actions/check-org-membership/action.yml diff --git a/.github/actions/check-org-membership/action.yml b/.github/actions/check-org-membership/action.yml new file mode 100644 index 0000000..8c28c8b --- /dev/null +++ b/.github/actions/check-org-membership/action.yml @@ -0,0 +1,98 @@ +name: Check Organization Membership +description: Checks if a user is authorized via repository permissions or organization membership + +inputs: + trigger-command: + description: 'The trigger command to check for (e.g., @claude or /oc-codex)' + required: true + github-token: + description: 'GitHub token with org membership read permissions' + required: true + event-name: + description: 'The GitHub event name (github.event_name)' + required: true + comment-body: + description: 'The comment body if applicable' + required: false + default: '' + comment-author-association: + description: 'The comment author association if applicable' + required: false + default: '' + review-body: + description: 'The review body if applicable' + required: false + default: '' + review-author-association: + description: 'The review author association if applicable' + required: false + default: '' + issue-body: + description: 'The issue body if applicable' + required: false + default: '' + issue-title: + description: 'The issue title if applicable' + required: false + default: '' + issue-author-association: + description: 'The issue author association if applicable' + required: false + default: '' + actor: + description: 'The GitHub actor (github.actor)' + required: true + organization: + description: 'The organization name to check membership in' + required: true + default: 'liatrio-labs' + +outputs: + is-authorized: + description: 'Whether the user is authorized to trigger the workflow' + value: ${{ steps.check.outputs.authorized }} + +runs: + using: 'composite' + steps: + - name: Check authorization + id: check + shell: bash + env: + GH_TOKEN: ${{ inputs.github-token }} + TRIGGER_COMMAND: ${{ inputs.trigger-command }} + EVENT_NAME: ${{ inputs.event-name }} + COMMENT_BODY: ${{ inputs.comment-body }} + COMMENT_AUTHOR_ASSOC: ${{ inputs.comment-author-association }} + REVIEW_BODY: ${{ inputs.review-body }} + REVIEW_AUTHOR_ASSOC: ${{ inputs.review-author-association }} + ISSUE_BODY: ${{ inputs.issue-body }} + ISSUE_TITLE: ${{ inputs.issue-title }} + ISSUE_AUTHOR_ASSOC: ${{ inputs.issue-author-association }} + ACTOR: ${{ inputs.actor }} + ORGANIZATION: ${{ inputs.organization }} + run: | + # Determine the author association based on event type + if [[ "$EVENT_NAME" == "issue_comment" ]] || [[ "$EVENT_NAME" == "pull_request_review_comment" ]]; then + AUTHOR_ASSOC="$COMMENT_AUTHOR_ASSOC" + elif [[ "$EVENT_NAME" == "pull_request_review" ]]; then + AUTHOR_ASSOC="$REVIEW_AUTHOR_ASSOC" + elif [[ "$EVENT_NAME" == "issues" ]]; then + AUTHOR_ASSOC="$ISSUE_AUTHOR_ASSOC" + fi + + # Check if user is a repo collaborator/owner/member first + if [[ "$AUTHOR_ASSOC" == "OWNER" ]] || [[ "$AUTHOR_ASSOC" == "MEMBER" ]] || [[ "$AUTHOR_ASSOC" == "COLLABORATOR" ]]; then + echo "User is authorized via author_association: $AUTHOR_ASSOC" + echo "authorized=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Check if user is a member of the organization + if gh api "orgs/$ORGANIZATION/members/$ACTOR" --silent 2>/dev/null; then + echo "User is authorized as $ORGANIZATION organization member" + echo "authorized=true" >> "$GITHUB_OUTPUT" + else + echo "User is not authorized" + echo "authorized=false" >> "$GITHUB_OUTPUT" + fi diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 858542e..fd5c41e 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -33,40 +33,27 @@ jobs: ) ) outputs: - is-authorized: ${{ steps.check.outputs.authorized }} + is-authorized: ${{ steps.check.outputs.is-authorized }} steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Check authorization id: check - env: - GH_TOKEN: ${{ secrets.ORG_MEMBER_CHECK_TOKEN }} - run: | - ACTOR="${{ github.actor }}" - - # Check if user is a repo collaborator/owner/member first - if [[ "${{ github.event_name }}" == "issue_comment" ]]; then - AUTHOR_ASSOC="${{ github.event.comment.author_association }}" - elif [[ "${{ github.event_name }}" == "pull_request_review_comment" ]]; then - AUTHOR_ASSOC="${{ github.event.comment.author_association }}" - elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then - AUTHOR_ASSOC="${{ github.event.review.author_association }}" - elif [[ "${{ github.event_name }}" == "issues" ]]; then - AUTHOR_ASSOC="${{ github.event.issue.author_association }}" - fi - - if [[ "$AUTHOR_ASSOC" == "OWNER" ]] || [[ "$AUTHOR_ASSOC" == "MEMBER" ]] || [[ "$AUTHOR_ASSOC" == "COLLABORATOR" ]]; then - echo "User is authorized via author_association: $AUTHOR_ASSOC" - echo "authorized=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Check if user is a member of liatrio-labs organization - if gh api "orgs/liatrio-labs/members/$ACTOR" --silent 2>/dev/null; then - echo "User is authorized as liatrio-labs organization member" - echo "authorized=true" >> "$GITHUB_OUTPUT" - else - echo "User is not authorized" - echo "authorized=false" >> "$GITHUB_OUTPUT" - fi + uses: ./.github/actions/check-org-membership + with: + trigger-command: '@claude' + github-token: ${{ secrets.ORG_MEMBER_CHECK_TOKEN }} + event-name: ${{ github.event_name }} + comment-body: ${{ github.event.comment.body || '' }} + comment-author-association: ${{ github.event.comment.author_association || '' }} + review-body: ${{ github.event.review.body || '' }} + review-author-association: ${{ github.event.review.author_association || '' }} + issue-body: ${{ github.event.issue.body || '' }} + issue-title: ${{ github.event.issue.title || '' }} + issue-author-association: ${{ github.event.issue.author_association || '' }} + actor: ${{ github.actor }} + organization: 'liatrio-labs' claude: needs: check-org-membership diff --git a/.github/workflows/opencode-gpt-5-codex.yml b/.github/workflows/opencode-gpt-5-codex.yml index c87a30c..799ce99 100644 --- a/.github/workflows/opencode-gpt-5-codex.yml +++ b/.github/workflows/opencode-gpt-5-codex.yml @@ -33,40 +33,27 @@ jobs: ) ) outputs: - is-authorized: ${{ steps.check.outputs.authorized }} + is-authorized: ${{ steps.check.outputs.is-authorized }} steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Check authorization id: check - env: - GH_TOKEN: ${{ secrets.ORG_MEMBER_CHECK_TOKEN }} - run: | - ACTOR="${{ github.actor }}" - - # Check if user is a repo collaborator/owner/member first - if [[ "${{ github.event_name }}" == "issue_comment" ]]; then - AUTHOR_ASSOC="${{ github.event.comment.author_association }}" - elif [[ "${{ github.event_name }}" == "pull_request_review_comment" ]]; then - AUTHOR_ASSOC="${{ github.event.comment.author_association }}" - elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then - AUTHOR_ASSOC="${{ github.event.review.author_association }}" - elif [[ "${{ github.event_name }}" == "issues" ]]; then - AUTHOR_ASSOC="${{ github.event.issue.author_association }}" - fi - - if [[ "$AUTHOR_ASSOC" == "OWNER" ]] || [[ "$AUTHOR_ASSOC" == "MEMBER" ]] || [[ "$AUTHOR_ASSOC" == "COLLABORATOR" ]]; then - echo "User is authorized via author_association: $AUTHOR_ASSOC" - echo "authorized=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Check if user is a member of liatrio-labs organization - if gh api "orgs/liatrio-labs/members/$ACTOR" --silent 2>/dev/null; then - echo "User is authorized as liatrio-labs organization member" - echo "authorized=true" >> "$GITHUB_OUTPUT" - else - echo "User is not authorized" - echo "authorized=false" >> "$GITHUB_OUTPUT" - fi + uses: ./.github/actions/check-org-membership + with: + trigger-command: '/oc-codex' + github-token: ${{ secrets.ORG_MEMBER_CHECK_TOKEN }} + event-name: ${{ github.event_name }} + comment-body: ${{ github.event.comment.body || '' }} + comment-author-association: ${{ github.event.comment.author_association || '' }} + review-body: ${{ github.event.review.body || '' }} + review-author-association: ${{ github.event.review.author_association || '' }} + issue-body: ${{ github.event.issue.body || '' }} + issue-title: ${{ github.event.issue.title || '' }} + issue-author-association: ${{ github.event.issue.author_association || '' }} + actor: ${{ github.actor }} + organization: 'liatrio-labs' opencode: needs: check-org-membership From a4a675ff9ab20cbab6606f5703996e4c1a33a93d Mon Sep 17 00:00:00 2001 From: Gregg Coppen Date: Mon, 27 Oct 2025 10:28:40 -0700 Subject: [PATCH 4/5] Update .github/actions/check-org-membership/action.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .github/actions/check-org-membership/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/check-org-membership/action.yml b/.github/actions/check-org-membership/action.yml index 8c28c8b..ef00771 100644 --- a/.github/actions/check-org-membership/action.yml +++ b/.github/actions/check-org-membership/action.yml @@ -89,7 +89,7 @@ runs: fi # Check if user is a member of the organization - if gh api "orgs/$ORGANIZATION/members/$ACTOR" --silent 2>/dev/null; then + if gh api "orgs/$ORGANIZATION/members/$ACTOR"; then echo "User is authorized as $ORGANIZATION organization member" echo "authorized=true" >> "$GITHUB_OUTPUT" else From 006097d131757486e18c02a451999b043b636c13 Mon Sep 17 00:00:00 2001 From: Gregg Coppen Date: Mon, 27 Oct 2025 10:32:57 -0700 Subject: [PATCH 5/5] Add explicit permissions block to check-org-membership jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added minimal 'contents: read' permission to check-org-membership jobs in both claude.yml and opencode-gpt-5-codex.yml workflows. This follows security best practices by explicitly limiting GITHUB_TOKEN permissions instead of relying on broad defaults, adhering to the principle of least privilege. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/claude.yml | 2 ++ .github/workflows/opencode-gpt-5-codex.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index fd5c41e..5565b3e 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -14,6 +14,8 @@ jobs: # Check if the user is a member of liatrio-labs organization check-org-membership: runs-on: ubuntu-latest + permissions: + contents: read if: | ( github.event_name == 'issue_comment' && diff --git a/.github/workflows/opencode-gpt-5-codex.yml b/.github/workflows/opencode-gpt-5-codex.yml index 799ce99..d4fc270 100644 --- a/.github/workflows/opencode-gpt-5-codex.yml +++ b/.github/workflows/opencode-gpt-5-codex.yml @@ -14,6 +14,8 @@ jobs: # Check if the user is a member of liatrio-labs organization check-org-membership: runs-on: ubuntu-latest + permissions: + contents: read if: | ( github.event_name == 'issue_comment' &&