Skip to content

Promote develop → main: CLI custom headers (#59) + webhook idempotency (#68)#73

Merged
dvcdsys merged 6 commits into
mainfrom
develop
Jun 5, 2026
Merged

Promote develop → main: CLI custom headers (#59) + webhook idempotency (#68)#73
dvcdsys merged 6 commits into
mainfrom
develop

Conversation

@dvcdsys
Copy link
Copy Markdown
Owner

@dvcdsys dvcdsys commented Jun 5, 2026

Promotes the changes currently on develop to main.

Included

Verification

go build ./..., go vet ./..., go test ./... green on the CLI module; both source PRs reviewed and merged into develop.

🤖 Generated with Claude Code

dvcdsys and others added 6 commits June 4, 2026 19:49
Auto-registration always POSTed a new hook instead of checking whether
one with the same delivery URL already existed, so re-adds, reindexes,
and restarts accumulated duplicate hooks on a repo — GitHub then fanned
every push out N times.

Add githubapi.ListWebhooks + EnsureWebhook: list existing hooks, match on
config.url, PATCH the match (and prune same-URL duplicates) instead of
creating, and only POST when none match. Wire both registration paths
(tryAutoRegisterWebhook, reconciler.reconcileOne) through it. The
reconciler now reuses an existing hook even when the stored WebhookID was
lost, so a reregister sweep never leaves old + new hooks side by side.

Covered by new githubapi tests (create / reuse / prune-duplicates /
ignore-non-matching-URL) and a reconciler reuse test. Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…doc caveat

Address PR review:
- When the matched hook is deleted between list and PATCH (404), the
  create-replacement branch now also prunes any other same-URL
  duplicates we listed, instead of leaving them behind.
- WEBHOOKS.md §6: note that a reconcile sweep does NOT prune pre-existing
  same-URL duplicates when the stored hook id is still valid (it PATCHes
  that one and returns); re-adding the repo collapses them via
  EnsureWebhook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(server): make GitHub webhook registration idempotent (#68)
Self-hosting cix behind an authenticating reverse proxy / Zero-Trust
gateway (Cloudflare Access, oauth2-proxy, Authelia) blocks the CLI and
AI-agent tooling at the edge: they send only the cix Bearer and have no
way to satisfy the proxy off-VPN, so they get a 302/403 before reaching
cix. The browser dashboard passes via interactive SSO and is unaffected.

Add opt-in per-server custom headers attached to every outbound request
(including the previously header-less /health probe) in addition to the
cix Bearer — e.g. a Cloudflare Access service token. The proxy validates
and strips them at the edge, so cix needs no knowledge of the proxy.
No server-side changes.

- config: ServerEntry.Headers (sensitive, omitempty) + SetServerHeader /
  UnsetServerHeader + validateHeader (RFC 7230 token names, anti-CRLF).
- CLI: `cix config set/unset server.<name>.header.<Name>`; values never
  echoed; `cix config show` and TUI surface a count only.
- client: SetCustomHeaders + applyCustomHeaders, applied in do(), Health()
  and the streaming path, always BEFORE cix-managed headers so a stray
  config can't clobber Authorization.
- getClient: ${ENV}-expand values into a throwaway copy (never written
  back), validate after expansion, fail without echoing the value.
- docs: CLI_CONFIG.md section + SECURITY_DEPLOYMENT.md cross-link.

Tests: config round-trip/validation, client apply + Authorization
precedence, and an e2e getClient test proving ${ENV} reaches the wire
while the config file keeps the placeholder.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address two footguns in the ${ENV} expansion of custom request headers,
both found in review of #59:

1. An unset (typo'd / unexported) variable used to expand to "" via
   os.ExpandEnv and be sent as an empty header — bouncing at the proxy
   with an opaque 403 and no hint. Now ExpandEnvHeaderValue treats a
   referenced-but-unset variable as a hard error that NAMES the variable
   (never the value). A set-but-empty var is still honored as intentional.
2. os.ExpandEnv mangled a literal `$` in a value (e.g. `pa$$word` →
   `pa`). The new expander supports `$$` as an escape for a literal `$`,
   so values containing `$` survive intact.

Also documents header-name canonicalization (CF- → Cf-, harmless since
HTTP header names are case-insensitive) in CLI_CONFIG.md.

Tests: ExpandEnvHeaderValue table (escapes, set/empty/unset) and a
getClient test proving an unset header env var fails loudly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(cli): support custom HTTP request headers per server (#59)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant