Refactor: upgrade deps, improve reliability, update docs and tooling#4
Merged
cbullinger merged 46 commits intomainfrom Feb 20, 2026
Merged
Refactor: upgrade deps, improve reliability, update docs and tooling#4cbullinger merged 46 commits intomainfrom
cbullinger merged 46 commits intomainfrom
Conversation
The workflow processor was creating UploadKey with BranchPath set to just the branch name (e.g., 'main') instead of the full ref path (e.g., 'refs/heads/main'). This caused GitHub API calls to fail with 404 errors when trying to access the branch ref. This fix ensures BranchPath is always set with the 'refs/heads/' prefix, consistent with how it's used throughout the rest of the codebase.
- Add GetRestClientForOrg() to get installation-specific tokens - Fix GraphQL query to use node(id:) instead of repository(owner:) - Update RetrieveFileContentsWithConfigAndBranch to use org-specific client - Remove refs/heads/ prefix duplication in workflow processor - Fixes 404 errors when accessing repos in different orgs
- Add explicit 'GITHUB APP AUTHENTICATION FAILED' message for 401 errors - Point users to check CODE_COPIER_PEM secret in GCP Secret Manager - Add detection in getInstallationIDForOrg, getInstallationAccessToken - Add detection in config_loader and main_config_loader when fetching configs This makes it immediately obvious when the PEM key is invalid/expired instead of showing misleading 'failed to load config' errors.
golangci-lint v1.x is built with Go 1.24 and can't analyze Go 1.26 code. Pin to v2.9.0 which supports Go 1.26. Similarly, the gosec Docker action bundles Go 1.25.7 — switch to `go install` so it uses the Go 1.26 from setup-go. Co-authored-by: Cursor <cursoragent@cursor.com>
golangci-lint-action v6 doesn't support golangci-lint v2. Switch to action v7 which does. gosec @latest includes new taint analysis rules (G703-G706) that flag all http.Client.Do() calls as SSRF and CLI os.ReadFile as path traversal. These are false positives for this codebase — exclude globally alongside the existing G115 exclusion. Co-authored-by: Cursor <cursoragent@cursor.com>
- Wrap deferred Close() calls to satisfy errcheck - Replace deprecated github.String/Int/Bool with github.Ptr (SA1019) - Apply De Morgan's law to simplify boolean expressions (QF1001) - Remove unused parseIntWithDefault function - Use t.Setenv in tests instead of unchecked os.Setenv (errcheck) - Use system golangci-lint v2.9.0 in pre-commit for full coverage Co-authored-by: Cursor <cursoragent@cursor.com>
Add EffectiveConfigFile() method to Config that returns the actual config file in use (MainConfigFile when USE_MAIN_CONFIG=true, otherwise ConfigFile). Use it in the startup banner and main_config_loader. Co-authored-by: Cursor <cursoragent@cursor.com>
…rget UploadKey now includes CommitStrategy, so workflows targeting the same repo/branch with different strategies (direct vs pull_request) produce independent write operations instead of silently merging into one batch. Also adds a config-load-time warning for mixed strategies, enriches write-phase log messages with strategy_source and file_count, and includes a test verifying the new behavior. Co-authored-by: Cursor <cursoragent@cursor.com>
…ier PRs #3: workflow_processor now logs when a subsequent workflow in the same batch overwrites the commit message or PR title, showing both old and new values so the "last wins" behavior is transparent. #5: addFilesViaPR now checks for an existing open PR from a copier/* branch before creating a new one. If found, pushes to the existing branch and updates the PR title/body instead of creating a duplicate. Co-authored-by: Cursor <cursoragent@cursor.com>
…llel - Add CachedConfigLoader with configurable TTL (default 5min) that wraps ConfigLoader to avoid re-resolving YAML on every webhook - Refactor ProcessWorkflow into match/fetch/queue phases, fetching file contents concurrently via errgroup (max 5 parallel fetches) - Mark RECOMMENDATIONS.md items #13 and #14 as resolved Co-authored-by: Cursor <cursoragent@cursor.com>
Before creating a commit, compare the new tree SHA against the base commit's tree SHA. If identical (e.g., duplicate webhook processing where changes are already at HEAD), skip the commit and log a message instead of creating an empty 0-change commit. Applies to both the direct-commit and PR-via-temp-branch code paths. Marks RECOMMENDATIONS.md item #2 as resolved. Co-authored-by: Cursor <cursoragent@cursor.com>
- #9: Wrap background webhook processing in context.WithTimeout (configurable via WEBHOOK_PROCESSING_TIMEOUT_SECONDS, default 5min) - #7: Add exponential-backoff retry via processWebhookWithRetry (configurable via WEBHOOK_MAX_RETRIES and WEBHOOK_RETRY_INITIAL_DELAY); panics are converted to errors; Slack alert sent after exhaustion - #6: processFilesWithWorkflows now returns per-workflow error map so one failing workflow doesn't block others; aggregate error enables retry of the full processing attempt Co-authored-by: Cursor <cursoragent@cursor.com>
- #10: Rotate test PEM key in .env.test to a purpose-generated test-only key; update .gitleaksignore with new fingerprint - #16: Add .golangci.yml (v2 format) pinning linters, formatters, and documenting suppressed rules; auto-fix pre-existing gofmt issues - #23: Add DeliveryID and Attempts fields to ErrorEvent; thread delivery ID through processWebhookWithRetry to Slack alerts - #19: Add TestIntegration_TargetRepoBatching_MixedStrategies verifying 2 direct workflows batch into 1 commit while a PR workflow produces a separate PR; fix existing integration test to mock GetCommit for empty-commit detection Co-authored-by: Cursor <cursoragent@cursor.com>
- golangci.yml: move exclude-rules from issues to exclusions (v2 schema) - Add #nosec annotations for gosec taint analysis rules (G703-G706) - G704 (SSRF): hardcoded api.github.com URLs in auth and CLI tools - G703 (path traversal): CLI tool reads user-provided file path - G705 (XSS): CLI tool writes to stderr, not web output - G706 (log injection): structured slog with key-value pairs Co-authored-by: Cursor <cursoragent@cursor.com>
- golangci.yml: move exclusions under linters (v2 schema nests it there) - slack_notifier.go: add #nosec G704 for Slack webhook URL Co-authored-by: Cursor <cursoragent@cursor.com>
…egex Two bug fixes: 1. Deprecation file location: The deprecation file (deprecated_examples.json) is now correctly read from and written to the source repository where files are being deleted, instead of the config repository. Also handles creating the file if it doesn't exist. 2. Exclude patterns: Changed workflow exclude patterns to use regex matching (as documented) instead of glob matching. This fixes exclude patterns like ".*_test\.ts$" not working. Co-authored-by: Cursor <cursoragent@cursor.com>
- Add .cursorignore to exclude binaries, secrets, IDE files, and test fixtures from Cursor indexing - Update AGENT.md with missing files (config_cache.go, utility scripts) - Add release process documentation and Quick Reference section - Add Key Documentation table for discoverability Co-authored-by: Cursor <cursoragent@cursor.com>
…lows When multiple workflows target the same repo with different auto_merge settings, log a warning and use AND logic (any false wins) for safety.
9d87645 to
c2383d9
Compare
MongoCaleb
approved these changes
Feb 20, 2026
Collaborator
MongoCaleb
left a comment
There was a problem hiding this comment.
A couple of Qs. LTGM.
| # PEM_NAME=CODE_COPIER_PEM | ||
| # GOOGLE_CLOUD_PROJECT_ID=github-copy-code-examples | ||
|
|
||
| # Option B: Provide the PEM key directly (no GCP access needed) |
Collaborator
There was a problem hiding this comment.
Edification question: It's been a long time since I visited this; do we now have a safe way to store a PEM?
Collaborator
Author
There was a problem hiding this comment.
it'd be only if you're testing locally, so it'd be stored in an uncommitted .env.local
| # Only set these if you want to test with actual GCP | ||
| # GCP_PROJECT_ID=your-project-id | ||
| # PEM_KEY_NAME=projects/123/secrets/CODE_COPIER_PEM/versions/latest | ||
| # A PAT is NOT used by the app itself — only by the test-webhook CLI tool |
Collaborator
There was a problem hiding this comment.
Do we want to spell out/define Personal Access Token here?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Major refactor covering dependency upgrades, reliability improvements, documentation overhaul, and tooling updates.
Dependency Upgrades
google/go-githubv48 → v82mongo-driverv1 → v2Reliability & Features
RateLimitTransportwith auto-retry on 403/429DeliveryTrackerdeduplicates viaX-GitHub-Deliveryheaderlog/slogwith JSON output for Cloud LoggingErrRateLimited,ErrNotFound, etc. inerrors.gosync.RWMutex/readyprobe separate from/healthlivenessDryRunflag now actually prevents writes (was cosmetic-only)DeprecatedFileEntry:source_path- Original source file path (for traceability)pr_number- PR that caused the deletion (for auditing)Slack: true/falseto the startup banner to show whether Slack notifications are enabledDocs & Config
QUICK-REFERENCE.md,DEBUG-LOGGING.md,RECOMMENDATIONS.md)/eventswebhook path,slogloggingapp.yaml(App Engine Flex config)github-app-manifest.ymldocumenting required permissions and eventsLOCAL-TESTING.mdwith GitHub App auth setup (SKIP_SECRET_MANAGERflow)WebserverPathfrom/webhookto/eventsScripts
convert-env-format.sh,convert-env-to-yaml.sh,validate-config-detailed.py)ci-local.sh(mirrors CI pipeline locally)/eventspathrelease.shto create a tag-based release systemCLI Tools (
cmd/)test-webhook: addedX-GitHub-Deliveryheader, fixed stale URLsconfig-validator:initnow supportsbasic/glob/regextemplates, fixedtype: "pr"→"pull_request"test-pem: rewritten with proper error handling, added READMECI
ci.ymlfor Go 1.26, added security scanning (gosec, Trivy)SERVICE_NAME: "examples-copier"to deploy to existing Cloud Run servicegithub-copiermain. Instead, it now requires a manual release viarelease.shscriptBug Fixes
Deprecation File Location Fix
deprecated_examples.json) was being read/written to the config repo instead of the source repoDuplicate Prevention on Webhook Redelivery
UpdateDeprecationFile()now checks for existing entries before appendingfilename|repo|branchcomposite key for deduplicationDeprecation Config
EnabledCheckDeprecationConfig.Enabledfield was never checked - deprecation tracking happened for ALL deleted files regardless of configaddToDeprecationMap()now only tracks deprecations whenworkflow.DeprecationCheck.Enabled == trueExclude Patterns Fix
Workflow.Excludepatterns weren't working because the code used glob matching but the documentation (and configs) specified regex patternsisExcluded()to use regex matching, consistent with documentation andSourcePattern.ExcludePatterns*_test.ts,*.spec.ts, and.eslintrc.*are now properly excludedAuto-Merge Batching Warning
auto_mergesettings, there was no indication of the conflictfalsedisables auto-merge for the batch)Deprecation Feature Enhancements (Latest Session)
Test plan
go build ./...passesgo test -race ./...passesgolangci-lint run ./...passesgrove-platform/github-copier→.copier/main.yaml)*_test.ts,*.spec.ts,.eslintrc.*filesDeprecationCheck.Enabled: truesource_pathandpr_numberfields/health,/ready,/metricsendpointsMade with Cursor