These are managed by upstream — our fork syncs deps via the
upstream-sync workflow. Keep Renovate active for .forgejo/kcl-ci/
dependencies (kcl.mod git tags).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When Claude creates a PR (e.g. upstream-sync), the pull_request event
fires with actor=claude. The self-trigger guard in actor.ts blocks
this because actor 'claude' matches bot_name 'claude[bot]', causing
test-action to fail with "Blocking self-trigger" error.
Add `if: github.actor != 'claude'` to the test-agent-mode job.
Claude-created PRs (upstream syncs) don't need the integration test
since they're merge commits, not action code changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add idempotency to the merge-upstream job:
1. Check for existing open PR from chore/upstream-sync-* branch
2. If PR exists and branch is up to date with upstream → skip
3. If PR exists but upstream has new commits → checkout existing
branch, merge new upstream commits, run Claude to review/test
4. If no PR exists → create new branch (existing behavior)
Also update Claude's prompt to handle both create and update PR
cases, so it knows to push to the existing PR branch instead of
creating a duplicate.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three fixes in the upstream-sync workflow:
1. Replace github.token with CLAUDE_BOT_TOKEN in the check-upstream
job. The default runner token on Forgejo doesn't have issue-write
permissions, so issues were silently failing to create.
2. Output the tracking issue number from check-upstream and pass it
to the merge-upstream job. Claude's prompt now includes the issue
reference and instructs it to add "Closes #N" in the PR body.
3. Capture the issue number from both create and update paths so
the merge job always has it available.
Also cleaned up 6 duplicate upstream-sync labels created by previous
runs that used the unprivileged token.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes for the upstream-sync Claude workflow:
1. Add Bun install + bun install (deps) steps before Claude runs,
so tsc, prettier, and other devDependencies are available.
Previously node_modules/.bin/ didn't exist and typecheck/format
commands failed with "command not found".
2. Add Bash(bunx:*), Bash(npx:*), Bash(./node_modules/.bin/*:*)
to allowed_tools. The existing Bash(bun:*) only covers the `bun`
binary, not `bunx` which is a separate binary.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CLAUDE.md: add "Things That Will Bite You" entry about bare Bash
requiring interactive approval and needing sub-patterns in CI
- docs/forgejo.md: update allowed_tools section with sub-pattern
examples, agent mode fix note, and common patterns reference
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Agent mode never passed ALLOWED_TOOLS_INPUT to the Claude SDK —
only tag mode did (via --allowedTools in claudeArgs). This meant
the allowed_tools action input was silently ignored in agent mode,
causing all Bash commands to require interactive approval in CI.
Pass ALLOWED_TOOLS_INPUT directly as options.allowedTools, which
the SDK merges with any tools from claudeArgs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace raw JSON dumps with concise, scannable log lines when
show_full_output is enabled. Adds an onMessage callback to the SDK
runner that formats each message type into 1-2 line summaries with
emoji prefixes, tool-specific parameter display, result correlation
for parallel tool calls, and subagent progress tracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Picks up the allowed_tools fix from kcl-ci — claude.yml tag-mode
workflow now uses Bash(git:*),Bash(bun:*),Bash(curl:*) sub-patterns
instead of bare Bash, enabling auto-approval in CI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bare `Bash` in allowed_tools makes the tool available but individual
commands still require interactive approval. In CI there's nobody to
approve, so git push, bun test, and curl all fail with
"Error: This command requires approval".
Changes:
- Switch to Bash(git:*), Bash(bun:*), Bash(curl:*) sub-patterns
- Add GITHUB_SERVER_URL env var so Claude doesn't need `export`
- Update prompt to reflect env var is pre-set
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add show_full_output: true to both claude.yml and upstream-sync.yml
- Add allowed_tools (Bash, Read, Write, etc.) to upstream-sync merge step
- Upgrade kcl-ci to v1.37.0 (defaults show_full_output and full tools)
Fixes: Claude was burning 38 turns hitting sandbox permission denials
trying to run bun test and git push during upstream sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove wait-for-checks from kcl-ci-build (not needed for this repo).
Add .forgejo/workflows/ to .prettierignore since KCL controls formatting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New merge-upstream job: creates branch, merges upstream, runs Claude
agent mode to review, resolve conflicts, run tests, and create a PR
- Dynamic fork impact analysis: computes overlap between fork-modified
files and upstream-changed files to assess conflict risk
- Claude reviews ALL upstream changes for fork compatibility (renamed
imports, changed APIs, GitHub-only assumptions) not just conflicts
- Maintainability principle: minimize fork divergence, prefer adapters
- Remove ANTHROPIC_API_KEY (using OAuth token only)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add .forgejo/kcl-ci/ with kcl.mod, main.k, Taskfile.yaml
- claude.yml: use claude.k module (fixes #comment YAML validation error,
removes duplicate pull_request_comment trigger)
- ci.yml: add paths-ignore for kcl-ci paths, named jobs for check constants
- test-action.yml: regenerated from KCL (functionally identical)
- upstream-sync.yml: regenerated from KCL (functionally identical)
- kcl-ci-build.yml: new auto-regeneration workflow (waits for CI checks)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ignore .github/, base-action/, and node_modules/ — these are
upstream-owned and managed via upstream-sync, not Renovate.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The disallowed tools env var read in run.ts was DISALLOWED_TOOLS_INPUT
(raw user input) instead of DISALLOWED_TOOLS (computed value including
WebSearch/WebFetch defaults). This meant default tool disallowance was
never enforced. Also removes orphaned ALLOWED_TOOLS env var export from
createPrompt() (tag mode passes allowed tools via claudeArgs instead),
extracts shared parseToolsInput utility to eliminate duplication, and
adds tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Forgejo REST API returns state as lowercase ("closed"/"open") but
branch.ts compares against GraphQL-style uppercase ("CLOSED"/"MERGED").
This caused @claude on closed/merged PRs to take the open-PR checkout
path, failing with "fatal: invalid reference: refs/pull/N/head".
Normalize state to uppercase in the Forgejo data fetcher, matching the
existing pattern used for review state (line 153).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Forgejo's pull_request_comment event uses an issue_comment-like payload
with issue.number instead of pull_request.number. Add fallback to
extract entity number from issue field when pull_request is absent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Removed unused re-exports (CommentUpdateInput, ExecutionDetails,
ensureProperlyEncodedUrl) from forgejo/comment-logic.ts
- Verified no `any` type escapes in Forgejo-specific code
- Confirmed naming conventions consistent with upstream
- No dead code, unused imports, or unreachable branches
- All complex decisions documented with comments
- Typecheck, format, and 834 tests pass clean
Progress: 83% complete — M6 (documentation accuracy) remaining
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- F1: Replace process.exit(1) with throw in branch.ts (cleanup was bypassed)
- F2: Replace process.exit(1) with throw in install-mcp-server.ts (same issue)
- F2b: Replace process.exit(1) with throw in create-prompt/index.ts (same issue)
- F3: Use getServerUrl() instead of GITHUB_SERVER_URL constant in branch-cleanup.ts
- F4: Use updated_at for review TOCTOU filtering in Forgejo data fetcher
- Accepted notes: no pagination >100, no retry, unconditional trigger, image no-op
Progress: 33% complete — M2 done, M3 (security review) next
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Branch creation, commit push, bot attribution all verified (run #198)
- 1 bug found: prompt template used quick_pull=1 instead of expand=1
- Fix committed in f08b1b4, validates after merge to main
Progress: 87% complete — M7 (docs update) remaining
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The prompt template hardcoded quick_pull=1 (GitHub-specific) in the
"Create a PR" URL. On Forgejo, this parameter is ignored so the
compare page doesn't auto-expand the PR creation form. Forgejo uses
expand=1 for this purpose.
Since Claude writes the PR URL into the comment body, and
updateForgejoCommentBody() extracts it (prLinkFromContent) with
priority over the URL generated by update-comment-link.ts, the
prompt template is the correct place to fix this.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add inline comments explaining:
- Forgejo never fires pull_request_review (sends pull_request_comment)
- Why workflow if-condition cannot gate on @claude for these events
(payload fields are empty, confirmed by run 12084 being skipped)
- The @claude check is handled in trigger.ts via payload inspection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Forgejo's pull_request_comment payload does not populate
github.event.comment.body or github.event.review.body in a way
the workflow contains() expression can read, causing legitimate
@claude reviews to be skipped (run 12084).
Revert to unconditional pass at the workflow level. The @claude
check is handled properly in trigger.ts which inspects the actual
payload fields with fallback logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously, all pull_request_comment events on Forgejo triggered the
full Claude pipeline unconditionally — both in the workflow if-condition
and in trigger.ts. This wasted CI resources and API credits on reviews
without @claude mentions (e.g. plain "LGTM" reviews).
Workflow: add @claude check via contains() on comment.body and review.body.
Trigger validation: replace blind auto-trigger with payload-aware check
that inspects available body fields, falling back only when no body exists.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Forgejo's issues/assigned webhook payload doesn't include a top-level
assignee object. The assignee is only available under issue.assignee.
Fall back to that field when payload.assignee is missing.
Also removes M5 debug logging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Forgejo runner's expression parser rejects '#' characters inside
if: | blocks (run 52778: "unexpected character '#' while lexing
expression"). Move all comments above the if: key and switch from
block scalar (|) to folded scalar (>-) to avoid multiline issues.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add inline comments explaining:
- Forgejo never fires pull_request_review (sends pull_request_comment)
- Why workflow if-condition cannot gate on @claude for these events
(payload fields are empty, confirmed by run 12084 being skipped)
- The @claude check is handled in trigger.ts via payload inspection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Forgejo's pull_request_comment payload does not populate
github.event.comment.body or github.event.review.body in a way
the workflow contains() expression can read, causing legitimate
@claude reviews to be skipped (run 12084).
Revert to unconditional pass at the workflow level. The @claude
check is handled properly in trigger.ts which inspects the actual
payload fields with fallback logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously, all pull_request_comment events on Forgejo triggered the
full Claude pipeline unconditionally — both in the workflow if-condition
and in trigger.ts. This wasted CI resources and API credits on reviews
without @claude mentions (e.g. plain "LGTM" reviews).
Workflow: add @claude check via contains() on comment.body and review.body.
Trigger validation: replace blind auto-trigger with payload-aware check
that inspects available body fields, falling back only when no body exists.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
E2E test confirmed: Claude correctly responds to @claude mentions in
PR review comments on Forgejo (run #12075, PR #15). Three bugs were
found and fixed during validation:
1. Missing comment object in Forgejo webhook payload
2. Event name mismatch (pull_request_comment vs pull_request_review_comment)
3. MCP server routing updates to wrong API (critical — silently dropped responses)
Progress: 3/8 milestones complete (37.5%)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On Forgejo, the tracking comment for pull_request_review_comment events
is always created as a regular issue comment (because Forgejo's webhook
payload lacks the top-level comment object). The MCP server was
incorrectly setting isPullRequestReviewComment=true based on the event
name, causing every update to try pulls.updateReviewComment first —
which 404'd and silently swallowed Claude's responses.
Three fixes:
- MCP server: detect Forgejo and always use issue comment path
- update-comment-link: skip pulls.getReviewComment on Forgejo (saves
one wasted 404 API call)
- update-claude-comment: expand fallback to also catch 422 (defense
in depth for Forgejo edge cases)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>