-
Notifications
You must be signed in to change notification settings - Fork 6
feat(sync): GHA workflow + PRD content rendering for issue-sync (t020) #543
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
Conversation
… (t020.2, t020.3) - Add .github/workflows/issue-sync.yml with 3 jobs: sync-on-push (auto-sync on TODO/PLANS changes), sync-on-issue (pull refs on issue events), and manual-sync (workflow_dispatch with command dropdown) - Update compose_issue_body() to render related PRD/task file content as collapsible <details> blocks instead of bare links - Includes find_related_files() with explicit ref: following and grep fallback - extract_file_summary() extracts first 30 lines (skips YAML frontmatter)
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
WalkthroughEnhancements to shell script for improved task file lookup with inline content summaries, plus a new GitHub Actions workflow enabling bi-directional synchronization between TODO.md and GitHub Issues with multiple trigger points and retry mechanisms. Changes
Sequence Diagram(s)sequenceDiagram
participant GitHub as GitHub
participant Workflow as GitHub Actions Workflow
participant Git as Git Repo
participant Helper as Helper Script
participant Issues as GitHub Issues
rect rgba(100, 200, 150, 0.5)
Note over GitHub,Helper: Push/Issue Event Trigger
GitHub->>Workflow: trigger (push/issue/dispatch)
end
rect rgba(150, 150, 200, 0.5)
Note over Workflow,Helper: Sync Operation
Workflow->>Git: checkout & config
Workflow->>Helper: execute sync command
Helper->>Git: read TODO.md & tasks
Helper->>Issues: query/create/update issues
Helper->>Git: extract file summaries
end
rect rgba(200, 150, 100, 0.5)
Note over Git,GitHub: Persist Changes
Helper->>Git: update TODO.md with refs
Workflow->>Git: commit (if modified)
Git->>Workflow: push (with retry logic)
Workflow->>GitHub: update remote
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Sun Feb 8 01:13:03 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @.agents/scripts/issue-sync-helper.sh:
- Around line 437-450: The parser can silently skip the whole file if a starting
frontmatter delimiter is never closed because in_content remains "true"; update
.agents/scripts/issue-sync-helper.sh to detect this EOF state and recover: after
the while IFS= read -r line loop ends, check if in_content == "true" and if so
log a warning about malformed frontmatter, reset in_content and past_frontmatter
to their default values, and re-run the line-processing logic for the file (or
re-open the file and reprocess) so the file is treated as having no frontmatter
rather than producing an empty summary; reference the in_content and
past_frontmatter variables and the existing read loop in your fix.
In @.github/workflows/issue-sync.yml:
- Around line 41-51: The run block in the "Check commit author" step (id:
check-author) directly interpolates github.event.head_commit.author.name into
the shell, allowing script injection; instead set AUTHOR via the step's env:
(e.g., AUTHOR: ${{ github.event.head_commit.author.name }}) and then reference
the safe $AUTHOR inside the run script, ensuring you keep it quoted when
compared (e.g., if [[ "$AUTHOR" == "GitHub Actions" ]] ...]) so the value is not
re-parsed by the shell.
- Around line 26-28: The current concurrency block uses group: ${{
github.workflow }}-${{ github.ref }} with cancel-in-progress: true which causes
runs from different events (e.g., push and issues) to share the same group and
cancel each other; change the concurrency grouping to include the event name or
job identifier (e.g., use ${{ github.event_name }} or include matrix/job name)
so issue-sync runs are scoped separately from push runs, or alternatively set
cancel-in-progress: false to allow queued runs to complete; update the
concurrency block (the group and cancel-in-progress entries) accordingly to use
${{ github.event_name }} or a per-job token to avoid canceling in-flight issue
syncs.
- Around line 140-153: The step "Check issue title format" is currently
interpolating github.event.issue.title directly into the run: block (variable
TITLE="${{ github.event.issue.title }}"), which allows script injection; fix it
by moving the issue title into a safe environment variable and always expand it
quoted inside the shell: in the check-issue step set env: ISSUE_TITLE: ${{
github.event.issue.title }} and in the run block use TITLE="$ISSUE_TITLE"
(always quote expansions like "$TITLE" and "$ISSUE_TITLE"), then perform the
pattern checks and TASK_ID extraction from the quoted variable (e.g., echo
"$TITLE" | grep -qE '^t[0-9]+' and TASK_ID=$(echo "$TITLE" | grep -oE
'^t[0-9]+(\.[0-9]+)*')), and use printf '%s' "$TITLE" when printing to avoid
executing content.
🧹 Nitpick comments (3)
.github/workflows/issue-sync.yml (3)
100-121: Retry logic is solid; consider logging rebase failures.The 3-attempt retry with increasing backoff is a good pattern for handling concurrent pushes. The
[skip ci]commit message is a nice secondary loop-prevention measure alongside the author check.One note: Line 111 silences rebase failures with
|| true. If a conflict occurs, the subsequent push will fail with a confusing error. Consider logging the rebase outcome:- git pull --rebase origin main || true + if ! git pull --rebase origin main; then + echo "Warning: rebase failed, aborting rebase" + git rebase --abort 2>/dev/null || true + fi
242-250: Passinputs.commandthrough an env variable for defense-in-depth.While the
choicetype constrains the value server-side, the same env-variable pattern used elsewhere is a good habit — and keeps the security posture consistent across all jobs in this workflow.Proposed fix
- name: Run sync command run: | chmod +x .agents/scripts/issue-sync-helper.sh chmod +x .agents/scripts/shared-constants.sh - COMMAND="${{ github.event.inputs.command }}" + COMMAND="$SYNC_COMMAND" echo "=== Running: issue-sync-helper.sh $COMMAND ===" bash .agents/scripts/issue-sync-helper.sh "$COMMAND" --verbose env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SYNC_COMMAND: ${{ github.event.inputs.command }}
180-196:closedevent doesn't auto-mark TODO.md task as done — verify this is intentional.When an issue is closed on GitHub (e.g., manually by a developer), the workflow only logs a message and doesn't update TODO.md. This means closing an issue on GitHub won't mark the corresponding task as
[x]in TODO.md, creating silent drift. If this is the intended "GitHub → TODO.md is manual" design, consider documenting it in the workflow comments or the help text.
| while IFS= read -r line; do | ||
| # Skip YAML frontmatter | ||
| if [[ "$line" == "---" ]] && [[ "$past_frontmatter" == "false" ]]; then | ||
| if [[ "$in_content" == "true" ]]; then | ||
| past_frontmatter=true | ||
| in_content=false | ||
| continue | ||
| fi | ||
| in_content=true | ||
| continue | ||
| fi | ||
| if [[ "$in_content" == "true" ]]; then | ||
| continue | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Malformed frontmatter (single ---) silently produces empty summary.
If a file contains an opening --- but no closing ---, in_content remains true for the entire file and every line is skipped, yielding an empty summary with no indication of why. This is a silent failure mode that could be confusing during debugging.
Consider adding a safeguard — e.g., if EOF is reached while in_content is still true, assume no frontmatter was present and re-read (or reset and reprocess).
As per coding guidelines, .agents/scripts/*.sh: "Reliability and robustness" and "Clear logging and feedback".
🤖 Prompt for AI Agents
In @.agents/scripts/issue-sync-helper.sh around lines 437 - 450, The parser can
silently skip the whole file if a starting frontmatter delimiter is never closed
because in_content remains "true"; update .agents/scripts/issue-sync-helper.sh
to detect this EOF state and recover: after the while IFS= read -r line loop
ends, check if in_content == "true" and if so log a warning about malformed
frontmatter, reset in_content and past_frontmatter to their default values, and
re-run the line-processing logic for the file (or re-open the file and
reprocess) so the file is treated as having no frontmatter rather than producing
an empty summary; reference the in_content and past_frontmatter variables and
the existing read loop in your fix.
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concurrency group may cause missed syncs for issue events.
Both push and issues events on main resolve to the same github.ref (refs/heads/main), so they share a single concurrency group. With cancel-in-progress: true, rapid-fire issue events (or a push arriving during an issue sync) will cancel in-flight runs, potentially losing ref:GH# updates.
Consider scoping the concurrency group per job or per event type, e.g.:
Proposed fix
concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
+ group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: trueAlternatively, set cancel-in-progress: false if you'd prefer queued runs to complete rather than be cancelled.
📝 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.
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} | |
| cancel-in-progress: true |
🤖 Prompt for AI Agents
In @.github/workflows/issue-sync.yml around lines 26 - 28, The current
concurrency block uses group: ${{ github.workflow }}-${{ github.ref }} with
cancel-in-progress: true which causes runs from different events (e.g., push and
issues) to share the same group and cancel each other; change the concurrency
grouping to include the event name or job identifier (e.g., use ${{
github.event_name }} or include matrix/job name) so issue-sync runs are scoped
separately from push runs, or alternatively set cancel-in-progress: false to
allow queued runs to complete; update the concurrency block (the group and
cancel-in-progress entries) accordingly to use ${{ github.event_name }} or a
per-job token to avoid canceling in-flight issue syncs.
| - name: Check commit author | ||
| id: check-author | ||
| run: | | ||
| # Prevent infinite loops: skip if this commit was made by GitHub Actions | ||
| AUTHOR="${{ github.event.head_commit.author.name }}" | ||
| if [[ "$AUTHOR" == "GitHub Actions" ]] || [[ "$AUTHOR" == "github-actions[bot]" ]]; then | ||
| echo "skip=true" >> "$GITHUB_OUTPUT" | ||
| echo "Skipping: commit was made by GitHub Actions (loop prevention)" | ||
| else | ||
| echo "skip=false" >> "$GITHUB_OUTPUT" | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script injection vulnerability: github.event.head_commit.author.name interpolated directly in run: block.
Confirmed by actionlint and Codacy. A crafted commit author name (e.g., containing "; malicious_command; echo ") would execute arbitrary code in the runner. Pass it through an environment variable instead.
Proposed fix
- name: Check commit author
id: check-author
run: |
# Prevent infinite loops: skip if this commit was made by GitHub Actions
- AUTHOR="${{ github.event.head_commit.author.name }}"
+ AUTHOR="$COMMIT_AUTHOR"
if [[ "$AUTHOR" == "GitHub Actions" ]] || [[ "$AUTHOR" == "github-actions[bot]" ]]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Skipping: commit was made by GitHub Actions (loop prevention)"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
+ env:
+ COMMIT_AUTHOR: ${{ github.event.head_commit.author.name }}📝 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.
| - name: Check commit author | |
| id: check-author | |
| run: | | |
| # Prevent infinite loops: skip if this commit was made by GitHub Actions | |
| AUTHOR="${{ github.event.head_commit.author.name }}" | |
| if [[ "$AUTHOR" == "GitHub Actions" ]] || [[ "$AUTHOR" == "github-actions[bot]" ]]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "Skipping: commit was made by GitHub Actions (loop prevention)" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Check commit author | |
| id: check-author | |
| run: | | |
| # Prevent infinite loops: skip if this commit was made by GitHub Actions | |
| AUTHOR="$COMMIT_AUTHOR" | |
| if [[ "$AUTHOR" == "GitHub Actions" ]] || [[ "$AUTHOR" == "github-actions[bot]" ]]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "Skipping: commit was made by GitHub Actions (loop prevention)" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| env: | |
| COMMIT_AUTHOR: ${{ github.event.head_commit.author.name }} |
🧰 Tools
🪛 actionlint (1.7.10)
[error] 43-43: "github.event.head_commit.author.name" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details
(expression)
🪛 GitHub Check: Codacy Static Code Analysis
[failure] 43-43: .github/workflows/issue-sync.yml#L43
Using variable interpolation ${{...}} with github context data in a run: step could allow an attacker to inject their own code into the runner.
🤖 Prompt for AI Agents
In @.github/workflows/issue-sync.yml around lines 41 - 51, The run block in the
"Check commit author" step (id: check-author) directly interpolates
github.event.head_commit.author.name into the shell, allowing script injection;
instead set AUTHOR via the step's env: (e.g., AUTHOR: ${{
github.event.head_commit.author.name }}) and then reference the safe $AUTHOR
inside the run script, ensuring you keep it quoted when compared (e.g., if [[
"$AUTHOR" == "GitHub Actions" ]] ...]) so the value is not re-parsed by the
shell.
| - name: Check issue title format | ||
| id: check-issue | ||
| run: | | ||
| TITLE="${{ github.event.issue.title }}" | ||
| # Only sync issues with t-number prefix (our convention) | ||
| if echo "$TITLE" | grep -qE '^t[0-9]+'; then | ||
| echo "sync=true" >> "$GITHUB_OUTPUT" | ||
| TASK_ID=$(echo "$TITLE" | grep -oE '^t[0-9]+(\.[0-9]+)*') | ||
| echo "task_id=$TASK_ID" >> "$GITHUB_OUTPUT" | ||
| echo "Issue #${{ github.event.issue.number }} matches task $TASK_ID" | ||
| else | ||
| echo "sync=false" >> "$GITHUB_OUTPUT" | ||
| echo "Issue #${{ github.event.issue.number }} does not have t-number prefix, skipping" | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script injection vulnerability: github.event.issue.title interpolated directly in run: block.
Same class of vulnerability as the commit author injection. Any GitHub user who can create an issue can inject arbitrary shell commands via a crafted issue title.
Proposed fix
- name: Check issue title format
id: check-issue
run: |
- TITLE="${{ github.event.issue.title }}"
+ TITLE="$ISSUE_TITLE"
# Only sync issues with t-number prefix (our convention)
if echo "$TITLE" | grep -qE '^t[0-9]+'; then
echo "sync=true" >> "$GITHUB_OUTPUT"
TASK_ID=$(echo "$TITLE" | grep -oE '^t[0-9]+(\.[0-9]+)*')
echo "task_id=$TASK_ID" >> "$GITHUB_OUTPUT"
- echo "Issue #${{ github.event.issue.number }} matches task $TASK_ID"
+ echo "Issue #${ISSUE_NUMBER} matches task $TASK_ID"
else
echo "sync=false" >> "$GITHUB_OUTPUT"
- echo "Issue #${{ github.event.issue.number }} does not have t-number prefix, skipping"
+ echo "Issue #${ISSUE_NUMBER} does not have t-number prefix, skipping"
fi
+ env:
+ ISSUE_TITLE: ${{ github.event.issue.title }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}📝 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.
| - name: Check issue title format | |
| id: check-issue | |
| run: | | |
| TITLE="${{ github.event.issue.title }}" | |
| # Only sync issues with t-number prefix (our convention) | |
| if echo "$TITLE" | grep -qE '^t[0-9]+'; then | |
| echo "sync=true" >> "$GITHUB_OUTPUT" | |
| TASK_ID=$(echo "$TITLE" | grep -oE '^t[0-9]+(\.[0-9]+)*') | |
| echo "task_id=$TASK_ID" >> "$GITHUB_OUTPUT" | |
| echo "Issue #${{ github.event.issue.number }} matches task $TASK_ID" | |
| else | |
| echo "sync=false" >> "$GITHUB_OUTPUT" | |
| echo "Issue #${{ github.event.issue.number }} does not have t-number prefix, skipping" | |
| fi | |
| - name: Check issue title format | |
| id: check-issue | |
| run: | | |
| TITLE="$ISSUE_TITLE" | |
| # Only sync issues with t-number prefix (our convention) | |
| if echo "$TITLE" | grep -qE '^t[0-9]+'; then | |
| echo "sync=true" >> "$GITHUB_OUTPUT" | |
| TASK_ID=$(echo "$TITLE" | grep -oE '^t[0-9]+(\.[0-9]+)*') | |
| echo "task_id=$TASK_ID" >> "$GITHUB_OUTPUT" | |
| echo "Issue #${ISSUE_NUMBER} matches task $TASK_ID" | |
| else | |
| echo "sync=false" >> "$GITHUB_OUTPUT" | |
| echo "Issue #${ISSUE_NUMBER} does not have t-number prefix, skipping" | |
| fi | |
| env: | |
| ISSUE_TITLE: ${{ github.event.issue.title }} | |
| ISSUE_NUMBER: ${{ github.event.issue.number }} |
🧰 Tools
🪛 actionlint (1.7.10)
[error] 142-142: "github.event.issue.title" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details
(expression)
🤖 Prompt for AI Agents
In @.github/workflows/issue-sync.yml around lines 140 - 153, The step "Check
issue title format" is currently interpolating github.event.issue.title directly
into the run: block (variable TITLE="${{ github.event.issue.title }}"), which
allows script injection; fix it by moving the issue title into a safe
environment variable and always expand it quoted inside the shell: in the
check-issue step set env: ISSUE_TITLE: ${{ github.event.issue.title }} and in
the run block use TITLE="$ISSUE_TITLE" (always quote expansions like "$TITLE"
and "$ISSUE_TITLE"), then perform the pattern checks and TASK_ID extraction from
the quoted variable (e.g., echo "$TITLE" | grep -qE '^t[0-9]+' and
TASK_ID=$(echo "$TITLE" | grep -oE '^t[0-9]+(\.[0-9]+)*')), and use printf '%s'
"$TITLE" when printing to avoid executing content.



Summary
.github/workflows/issue-sync.ymlwith 3 jobs for automated bi-directional sync between TODO.md/PLANS.md and GitHub Issuescompose_issue_body()to render related PRD/task file content as collapsible<details>blocks (instead of bare links)GHA Workflow Jobs
sync-on-pushmain(TODO.md, PLANS.md, todo/tasks/**)close→push→enrich→pull, commits backsync-on-issuepullto sync refs to TODO.mdmanual-syncworkflow_dispatchdropdownScript Changes
find_related_files()— follows explicitref:todo/tasks/references + greps for task ID mentionsextract_file_summary()— extracts first 30 lines from PRD/task files (skips YAML frontmatter)compose_issue_body()— renders file content in collapsible<details>blocks with fallback to linksTesting
bash -nsyntax check: passshellcheck -S error: zero violationsparse t131correctly renders PRD content in composed bodyCloses subtasks t020.2 (GHA workflow) and t020.3 (PRD content sync).
Summary by CodeRabbit