Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
name: Build Desktop (reusable)
on:
workflow_call:
permissions:
contents: read
actions: write
Comment on lines +16 to +18
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What GITHUB_TOKEN permissions are required for actions/upload-artifact v5, actions/cache v5, and gh release upload in 2026?

💡 Result:

actions/upload-artifact@v5: contents: read (works even with empty permissions in private repos, but contents: read explicitly for safety) [1]. Official docs do not specify beyond general repo access, and artifact upload uses a special token but GITHUB_TOKEN needs contents: read for checkout/context [2][3]. actions/cache@v5: actions: write (for saving/restoring caches, as cache is managed under Actions API) [4][3]. Docs confirm no explicit permissions listed, but related tools require actions: read/write [5]. gh release upload: contents: write (required for creating/updating releases and uploading assets via REST API /releases endpoint) [6][7][8][9]. Uses GH_TOKEN which maps to contents permission for repo write operations including releases [10].

Citations:


🌐 Web query:

Does actions/cache@v5 use GITHUB_TOKEN or a separate cache service token? GitHub Actions permissions

💡 Result:

actions/cache@v5 uses the GITHUB_TOKEN (via ${{ github.token }} context) to access GitHub's built-in cache service. It does not require a separate cache service token. The action internally calls GitHub's REST API endpoints for Actions cache (e.g., list, get, create, update caches), which require the GITHUB_TOKEN to have actions:read (for restore) and actions:write (for save) permissions [1][2][3][4][5][6]. By default, GITHUB_TOKEN has these permissions unless restricted in the workflow [3][7]. No external cache service or additional token is needed or mentioned in official docs or the action's README [8][9][2][10][11]. For explicit cache management (e.g., delete via gh cache), actions:write must be granted [1][4][7].

Citations:


🌐 Web query:

GitHub Actions cache token mechanism in 2026 - GITHUB_TOKEN vs runner cache service

💡 Result:

In 2026, GitHub Actions caching uses an internal “Actions cache service” authentication token (ACTIONS_RUNTIME_TOKEN / bearer token), not the same credential that you normally use as GITHUB_TOKEN for GitHub API calls. 1) What GITHUB_TOKEN is (and is for) - GITHUB_TOKEN is the automatically generated token in a workflow job; it can be used for authenticated GitHub API calls and its permissions are controlled via the workflow’s permissions settings. It expires after the job completes and is scoped to the repository that contains the workflow.[1][2] 2) What the cache service token is (and is for) - The cache action (actions/cache) talks to GitHub’s cache backend service over an internal HTTP API. In the actions/toolkit cache HTTP client used by actions/cache, the client reads a token from the environment variable ACTIONS_RUNTIME_TOKEN and sends it as a Bearer credential in requests to the cache service.[3] - The Docker BuildKit “gha” cache backend docs likewise describe cache v2 inputs as coming from ACTIONS_RESULTS_URL and ACTIONS_RUNTIME_TOKEN, describing token “see authentication”.[4][5] 3) How the two differ in practice (GITHUB_TOKEN vs cache token) - GITHUB_TOKEN: intended for GitHub API authentication and is the token you reference as secrets.GITHUB_TOKEN / github.token.[2] - Cache service token (ACTIONS_RUNTIME_TOKEN): intended for calling the cache/artifacts/runtime service endpoints that actions/cache uses. It’s provided by the runner for workflow/action execution contexts, and it is the token wired into the cache HTTP client (Bearer) to access the cache service endpoints.[3][6] 4) Does actions/cache fall back to GITHUB_TOKEN? - The published toolkit cache client code shown above specifically uses ACTIONS_RUNTIME_TOKEN as the token it loads for cache service communication.[3] - I did not find an authoritative 2026 doc explicitly stating “ACTIONS_RUNTIME_TOKEN == GITHUB_TOKEN”. What is clearly documented is that cache actions depend on their runtime environment variables for cache service access, while GITHUB_TOKEN is for GitHub API authentication.[3][2][5] 5) Security boundary note (why GitHub doesn’t want you storing secrets in caches) - GitHub explicitly recommends not storing sensitive information (including access tokens/credentials) in cached files because cache contents can be accessed via pull requests from forks / readers.[7] Bottom line - Use GITHUB_TOKEN for GitHub API calls. - For GitHub Actions cache upload/download via actions/cache (and cache service v2 clients), the authentication mechanism is the bearer token provided to the runtime as ACTIONS_RUNTIME_TOKEN (with cache service URLs like ACTIONS_RESULTS_URL), not GITHUB_TOKEN by itself.[3][4][2]

Citations:


🌐 Web query:

Official GitHub documentation actions/cache@v5 GITHUB_TOKEN permissions requirements

💡 Result:

Official GitHub documentation / behavior: actions/cache@v5 doesn’t require you to pass GITHUB_TOKEN explicitly. GitHub Actions provides a GITHUB_TOKEN automatically to each job, and workflow/job permissions: controls what that token can do for the job [1][2]. What permissions: you need for actions/cache@v5: - To allow the GITHUB_TOKEN to create/save or manage GitHub Actions caches used by actions/cache, you generally need the actions scope to be writable: set permissions: actions: write (commonly alongside contents: read if you also check out code). - This is supported by GitHub’s mechanism for configuring GITHUB_TOKEN permissions via permissions: at the workflow/job level [2][3]. - Cache-related guidance indicates actions: write is needed for cache management operations that hit the Actions cache REST API (e.g., delete or other operations beyond restore). For example, a documented fix states permissions: actions: write as the needed setting [4]. - GitHub Actions “actions cache” operations (like delete) also reference actions:write as the required permission in practice [5]. Example (least privilege for typical cache usage + checkout): - Job-level permissions: - contents: read - actions: write Notes / gotchas: - GITHUB_TOKEN is a GitHub App installation access token whose permissions are limited; you can control those permissions with permissions: [1][6]. - Forked pull requests are treated specially and may receive reduced GITHUB_TOKEN permissions (often read-only), which can restrict cache write/save behavior depending on the trigger/context [6][7].

Citations:


🏁 Script executed:

# Check if the file exists and read the relevant sections
if [ -f ".github/workflows/build-desktop.yml" ]; then
  echo "=== Permissions block (lines 16-18) ==="
  sed -n '15,20p' .github/workflows/build-desktop.yml
  
  echo -e "\n=== gh release upload at line 587 ==="
  sed -n '585,590p' .github/workflows/build-desktop.yml
  
  echo -e "\n=== macOS re-upload script at line 657 ==="
  sed -n '655,660p' .github/workflows/build-desktop.yml
  
  echo -e "\n=== All uses of gh release or release upload ==="
  grep -n "gh release\|release.*upload" .github/workflows/build-desktop.yml || echo "No matches found"
else
  echo "File not found"
fi

Repository: tinyhumansai/openhuman

Length of output: 1621


🏁 Script executed:

# Also check if actions/cache and actions/upload-artifact are used
echo "=== Actions used in workflow ==="
grep -n "uses.*actions/cache\|uses.*actions/upload-artifact" .github/workflows/build-desktop.yml || echo "Not found"

Repository: tinyhumansai/openhuman

Length of output: 364


Fix permissions block: contents: write required for gh release upload (line 587) — current contents: read will fail with 403.

The workflow calls gh release upload at line 587 and via scripts/release/upload-macos-artifacts.sh, which requires contents: write on the releases API. With only contents: read, release uploads will fail on the first run with with_release_upload: true.

The actions: write scope is unnecessary. While actions/cache@v5 appears in this workflow (5 times), it uses the runner's internal ACTIONS_RUNTIME_TOKEN, not GITHUB_TOKEN permissions—the cache service operates independently of the permissions: block.

Minimal correct set:

🛡️ Corrected scopes
 permissions:
-  contents: read
-  actions: write
+  contents: write

If you prefer least-privilege granularity, scope contents: write only to the release upload steps via step-level permissions: and leave the workflow default at contents: read.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
permissions:
contents: read
actions: write
permissions:
contents: write
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-desktop.yml around lines 16 - 18, The workflow's
permissions block currently sets "contents: read" which will cause the gh
release upload call (invoked by the step that runs
scripts/release/upload-macos-artifacts.sh and guarded by with_release_upload) to
fail with 403; change the workflow-level permissions to include "contents:
write" (or, if you prefer least-privilege, add step-level permissions: contents:
write specifically on the step that runs gh release upload /
scripts/release/upload-macos-artifacts.sh) and remove the unnecessary "actions:
write" scope.

inputs:
Comment on lines 14 to 19
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Validate the workflow YAML and confirm workflow_call.inputs is populated.
python - <<'PY'
import sys, yaml
with open('.github/workflows/build-desktop.yml') as f:
    try:
        doc = yaml.safe_load(f)
    except yaml.YAMLError as e:
        print("YAML parse error:", e); sys.exit(1)
wc = (doc.get('on') or doc.get(True) or {}).get('workflow_call') or {}
print("workflow_call.inputs keys:", list((wc.get('inputs') or {}).keys()))
print("top-level permissions:", doc.get('permissions'))
PY

Repository: tinyhumansai/openhuman

Length of output: 183


🏁 Script executed:

# First, let's view the current build-desktop.yml around lines 14-25 to see exact indentation
sed -n '14,25p' .github/workflows/build-desktop.yml | cat -A

Repository: tinyhumansai/openhuman

Length of output: 321


🏁 Script executed:

# Now test the corrected structure by creating a test YAML and validating it
python - <<'PY'
import sys, yaml

# Read current file
with open('.github/workflows/build-desktop.yml', 'r') as f:
    content = f.read()
    
# Show what we have at lines 14-25
lines = content.split('\n')
print("Current lines 14-25:")
for i in range(13, min(25, len(lines))):
    print(f"{i+1:3d}: {repr(lines[i])}")

print("\n" + "="*60)
print("Attempting to parse current file:")
try:
    doc = yaml.safe_load(content)
    print("ERROR: File parsed successfully (unexpected!)")
except yaml.YAMLError as e:
    print(f"✓ Parse error confirmed: {e}")
PY

Repository: tinyhumansai/openhuman

Length of output: 648


🏁 Script executed:

# Find the line containing "inputs:" to understand the structure
grep -n "inputs:" .github/workflows/build-desktop.yml | head -5

Repository: tinyhumansai/openhuman

Length of output: 81


🏁 Script executed:

# Get broader context: lines 1-30
sed -n '1,30p' .github/workflows/build-desktop.yml | cat -n

Repository: tinyhumansai/openhuman

Length of output: 1391


🏁 Script executed:

# Find where the on: block ends (jobs: keyword marks next top-level section)
grep -n "^jobs:" .github/workflows/build-desktop.yml

Repository: tinyhumansai/openhuman

Length of output: 76


🏁 Script executed:

# Get lines around the end of the on: block (inputs section)
sed -n '90,105p' .github/workflows/build-desktop.yml | cat -n

Repository: tinyhumansai/openhuman

Length of output: 843


🏁 Script executed:

# Test the proposed fix by creating corrected YAML
python - <<'PY'
import yaml

# Create the corrected structure
corrected_yaml = """
on:
  workflow_call:
    inputs:
      build_ref:
        description: Git ref to check out for the build (tag or SHA).
        type: string
        required: true
permissions:
  contents: read
  actions: write
jobs:
  build:
    runs-on: ubuntu-latest
"""

print("Testing corrected YAML structure:")
try:
    doc = yaml.safe_load(corrected_yaml)
    wc = (doc.get('on') or {}).get('workflow_call') or {}
    print(f"✓ YAML parses successfully")
    print(f"  workflow_call.inputs keys: {list((wc.get('inputs') or {}).keys())}")
    print(f"  top-level permissions: {doc.get('permissions')}")
except yaml.YAMLError as e:
    print(f"✗ Parse error: {e}")
PY

Repository: tinyhumansai/openhuman

Length of output: 225


🏁 Script executed:

# Now check line 587 to understand what permission is needed for gh release upload
sed -n '580,595p' .github/workflows/build-desktop.yml | cat -n

Repository: tinyhumansai/openhuman

Length of output: 811


🏁 Script executed:

# Verify the permission scope: check if there are other uses of contents: write needed
grep -n "gh release\|contents: write\|actions: write" .github/workflows/build-desktop.yml

Repository: tinyhumansai/openhuman

Length of output: 180


🏁 Script executed:

# Check the current permission block in context (lines 13-20)
sed -n '13,20p' .github/workflows/build-desktop.yml | cat -n

Repository: tinyhumansai/openhuman

Length of output: 250


🏁 Script executed:

# Test what the corrected permissions should be
python - <<'PY'
import yaml

# Test with correct permissions scope
corrected_yaml_with_full_inputs = """
on:
  workflow_call:
    inputs:
      build_ref:
        description: Git ref
        type: string
        required: true
      with_updater:
        description: desc
        type: boolean
        default: true
permissions:
  contents: write
jobs:
  build:
    runs-on: ubuntu-latest
"""

print("Testing corrected YAML with contents: write (for gh release upload):")
try:
    doc = yaml.safe_load(corrected_yaml_with_full_inputs)
    wc = (doc.get('on') or {}).get('workflow_call') or {}
    inputs = list((wc.get('inputs') or {}).keys())
    perms = doc.get('permissions')
    print(f"✓ YAML parses successfully")
    print(f"  workflow_call.inputs keys: {inputs}")
    print(f"  top-level permissions: {perms}")
    if 'build_ref' in inputs and 'with_updater' in inputs:
        print(f"✓ All inputs properly nested under workflow_call")
except yaml.YAMLError as e:
    print(f"✗ Parse error: {e}")
PY

Repository: tinyhumansai/openhuman

Length of output: 241


🏁 Script executed:

# Check what the actual fix should look like - find exact end of inputs block
sed -n '95,102p' .github/workflows/build-desktop.yml | cat -A

Repository: tinyhumansai/openhuman

Length of output: 402


🏁 Script executed:

# Verify where inputs block ends to understand complete fix scope
sed -n '19,100p' .github/workflows/build-desktop.yml | grep -n "^ " | tail -1

Repository: tinyhumansai/openhuman

Length of output: 91


🏁 Script executed:

# Check what permissions release upload actually requires
python - <<'PY'
# According to GitHub Actions documentation:
# - actions/checkout@v4: needs contents:read
# - actions/cache@v5: does NOT require actions:write (uses its own service token)
# - actions/upload-artifact@v5: does NOT require actions:write (uses artifact service, not GITHUB_TOKEN for uploads)
# - gh release upload: needs contents:write (releases are part of repository contents)

# The workflow uses "gh release upload" at line 587, so:
print("Permission requirements for this workflow:")
print("- contents:read  (for actions/checkout)")
print("- contents:write (for gh release upload at line 587)")
print("- actions:write  (NOT needed for cache, checkout, or upload-artifact)")
print("\nConclusion: permissions should be 'contents: write' (or 'contents: read' if release upload is disabled)")
print("The current 'contents: read + actions: write' is WRONG for two reasons:")
print("  1. actions:write is unnecessary")
print("  2. contents:read is insufficient for gh release upload (needs write)")
PY

Repository: tinyhumansai/openhuman

Length of output: 554


🏁 Script executed:

# Final test: create a complete valid YAML structure matching the file
python - <<'PY'
import yaml

# Simulate the corrected structure with proper indentation
corrected = """---
name: Build Desktop (reusable)
on:
  workflow_call:
    inputs:
      build_ref:
        description: Git ref to check out for the build (tag or SHA).
        type: string
        required: true
      tag:
        description: Tag name
        type: string
        required: true
      with_updater:
        description: When true, set WITH_UPDATER=true
        type: boolean
        default: true
permissions:
  contents: write
jobs:
  build:
    runs-on: ubuntu-latest
"""

print("Testing corrected structure (with contents:write):")
try:
    doc = yaml.safe_load(corrected)
    on_block = doc.get('on') or {}
    wc = on_block.get('workflow_call') or {}
    inputs = wc.get('inputs') or {}
    perms = doc.get('permissions') or {}
    
    print(f"✓ YAML parses successfully")
    print(f"  on.workflow_call.inputs: {list(inputs.keys())}")
    print(f"  permissions: {perms}")
    print(f"  jobs: {list(doc.get('jobs', {}).keys())}")
except yaml.YAMLError as e:
    print(f"✗ Parse error: {e}")
PY

Repository: tinyhumansai/openhuman

Length of output: 229


🏁 Script executed:

# Confirm the end of the on: block and structure
sed -n '14,100p' .github/workflows/build-desktop.yml | grep -E "^(on:|  workflow_call:|permissions:|jobs:)" | head -5

Repository: tinyhumansai/openhuman

Length of output: 106


Critical: permissions: block is inserted inside on.workflow_call:, breaking YAML parsing and orphaning inputs:.

The file fails to parse with error "mapping values are not allowed here" at line 19. The current indentation places permissions: at column 0 between workflow_call: and inputs:, which closes the on: block before inputs: is defined. The inputs: key at column 4 becomes a stray orphaned entry. This breaks the reusable workflow contract, making it uncallable by the staging and production release workflows.

permissions: must be a top-level key (peer of on: / jobs:) placed after the entire on: block, not inside it.

Major: Permission scope is wrong and insufficient for gh release upload at line 587.

The current contents: read + actions: write is incorrect:

  • actions: write is unnecessary (actions/cache and actions/upload-artifact do not require it; they use service-scoped tokens).
  • contents: read is insufficient for gh release upload, which requires contents: write since releases are repository contents. The upload will fail with 403 Forbidden.

The permission block should use contents: write to support release uploads.

🛠️ Proposed fix — move `permissions:` to top level after `on:` block
 on:
   workflow_call:
     inputs:
       build_ref:
         description: Git ref to check out for the build (tag or SHA).
         type: string
         required: true
       tag:
         description:
           Tag name used by GH Release uploads (e.g. v1.2.4) and by the staging
           standalone CLI artifact name (e.g. v1.2.4-staging).
         type: string
         required: true
       with_updater:
         description:
           When true, set `WITH_UPDATER=true` so the Tauri bundler emits the
           signed `.sig` updater artifacts the auto-updater consumes.
           Production sets this and assembles `latest.json` in a follow-on
           job; staging leaves it off because there is no manifest publish
           step to consume the .sig files — producing them just wastes
           signing time and pollutes the artifact tree.
         type: boolean
         default: true
+permissions:
+  contents: write
 jobs:
🧰 Tools
🪛 actionlint (1.7.12)

[error] 19-19: could not parse as YAML: mapping values are not allowed in this context

(syntax-check)

🪛 YAMLlint (1.38.0)

[error] 19-19: syntax error: mapping values are not allowed here

(syntax)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-desktop.yml around lines 14 - 19, The YAML has
`permissions:` incorrectly indented inside `on.workflow_call`, which breaks
parsing and orphanes `inputs:`; move the entire `permissions:` block out to
top-level (as a peer of `on:` and `jobs:`) after the `on:` block so
`on.workflow_call.inputs` remains nested correctly, and change its scope to
`contents: write` (remove the unnecessary `actions: write`) so operations like
`gh release upload` have the required write permission for repository contents.

build_ref:
description: Git ref to check out for the build (tag or SHA).
Expand Down
Loading