Skip to content

CXP-383 Enable GitHub last-activity sync - #29

Merged
JavierCarnelli-ConductorOne merged 11 commits into
mainfrom
feat/cxp-383
Sep 18, 2026
Merged

JavierCarnelli-ConductorOne merged 11 commits into
mainfrom
feat/cxp-383

Conversation

@JavierCarnelli-ConductorOne

@JavierCarnelli-ConductorOne JavierCarnelli-ConductorOne commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Adds a sync-last-activity config field (wired into both the PAT and GitHub App auth groups) and threads it through NewLambdaConnector, enabling GitHub org member last-activity tracking from baton-github #188.
  • Bumps the vendored baton-github dependency from v0.4.0 to v0.4.7. (baton-sdk is already v0.30.0 on main; this PR doesn't change it.)
  • Removes the local replace directive used during development/testing; the vendored code now comes straight from the published baton-github module.
  • Documents the new "Sync member last activity" feature: the two-step enablement (config flag + enabling the "GitHub Activity" resource type in Capabilities & configuration), the GitHub App permission needed for audit-log access, and the corrected invitation status (now Pending, not Unspecified).

Dependency bump impact (v0.4.0 → v0.4.7)

Applies to every existing install, regardless of whether sync-last-activity is enabled:

  • Invitation resources now emit RESOURCE_STATUS_PENDING instead of STATUS_UNSPECIFIED (baton-github #184).
  • Profile, status, and created-at attributes moved from trait-level to resource-level fields for User, Invitation, Team, OrgRole, App, and APIToken resources (baton-github #181, an SDK-deprecation cleanup).

These are one-time data-shape changes on the next sync; no action needed.

Test plan

  • go build ./..., go vet ./..., go test ./... pass
  • golangci-lint run ./... clean
  • Verified end-to-end against a simulated C1 instance: event feed runs, and after enabling the required tenant feature flag + resource type, last-activity dates populate on member profiles

🤖 Generated with Claude Code

@linear-code

linear-code Bot commented Sep 15, 2026

Copy link
Copy Markdown

CXP-383

AppPrivatekeyPath: ghc.AppPrivatekeyPath,
Org: ghc.Org,
DirectCollaboratorsOnly: ghc.DirectCollaboratorsOnly,
SyncLastActivity: ghc.SyncLastActivity,

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.

🟡 Suggestion: DefaultCapabilitiesBuilder (lines 45-58) deliberately enumerates every conditionally-registered builder (APITokenBuilder, EnterpriseRoleBuilder, LicenseBuilder) so the config-less capabilities output advertises them all, but the new usage-app / "GitHub Activity" syncer is not listed and this type does not implement EventFeeds. Since builder.GetCapabilities derives resource types from ResourceSyncers() and CAPABILITY_EVENT_FEED_V2 from EventFeeds(), the default capabilities will omit both — which contradicts docs/connector.mdx:401 ("on a brand-new connector, this resource type is included by default"). Note newUsageAppBuilder/newUsageEventFeed are unexported in baton-github v0.4.5, so this likely needs an upstream export before it can be wired here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Known, already discussed on baton-github#188: DefaultCapabilitiesBuilder only feeds the offline capabilities CLI/catalog artifact (pre-install marketplace listing), not the live per-instance capability check that actually gates real syncs — that one calls GetMetadata() against the real running connector, which already implements EventFeeds()/usage-app correctly. So this has no effect on whether the feature works for a configured connector; it's a cosmetic gap in the pre-install catalog listing only. Deliberately not fixing it here — it'd require exporting currently-private constructors from baton-github for a purely cosmetic win.

Comment thread docs/connector.mdx Outdated
Comment thread go.mod Outdated
@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Connector PR Review: CXP-383 Update the sdk's version and enable Usage Event Feeds

Blocking Issues: 1 | Suggestions: 1 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 365004977f7a.
Review mode: incremental since 8d5bfa6
View review run

Review Summary

The new commits since 8d5bfa6 touch only docs/connector.mdx (the audit-log requirements now cover both the PAT read:audit_log scope and the GitHub App Organization permissions > Administration: Read-only permission) and wrap the GitHub App field list in pkg/config/config.go for the 200-char revive line-length limit. That addresses the prior docs/connector.mdx:397 finding, and the prior go.mod finding is addressed too now that the PR description documents the v0.4.0 to v0.4.5 invitation-status and trait-to-resource-level attribute changes. The full PR diff was re-scanned for security and correctness: go.mod, go.sum, and vendor/modules.txt are consistent at baton-github v0.4.5 / baton-sdk v0.30.0 with the development replace ../baton-github directive removed, and every connector.*Builder signature this repo calls still matches the bumped dependency. The pkg/connector/connector.go default-capabilities finding remains open.

Security Issues

None found.

Correctness Issues

  • pkg/connector/connector.go:45-58 (previously reported, still open) — DefaultCapabilitiesBuilder omits the new usage-app ("GitHub Activity") syncer and does not implement EventFeeds, so the credential-less capabilities output advertises neither the resource type nor CAPABILITY_EVENT_FEED_V2 (see vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/connectorbuilder.go:475), contradicting the docs claim at docs/connector.mdx:404 that it is included by default on brand-new connectors. newUsageAppBuilder is unexported in baton-github, so closing this needs an exported constructor upstream or a docs correction here.

Suggestions

  • docs/connector.mdx:17-27 — the Capabilities table is not updated with the new "GitHub Activity" resource type this PR adds (criteria D1).
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Correctness Issues

In `pkg/connector/connector.go`:
- Around line 45-58: DefaultCapabilitiesBuilder().ResourceSyncers() returns the ten
  existing builders but not the new usage-app ("GitHub Activity") syncer, and
  defaultCapabilitiesBuilder does not implement
  connectorbuilder.EventFeeds(ctx) []connectorbuilder.EventFeed. The SDK derives
  CAPABILITY_EVENT_FEED_V2 only from a non-empty event-feed list
  (vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/connectorbuilder.go:475),
  so the credential-less capabilities output served via
  connectorrunner.WithDefaultCapabilitiesConnectorBuilderV2 in
  cmd/baton-github-enterprise/main.go:23 advertises neither the usage-app resource
  type nor the event-feed capability. That contradicts docs/connector.mdx:404, which
  tells users the "GitHub Activity" resource type is included by default on a
  brand-new connector. Fix one of two ways: (a) have baton-github export a
  constructor for its usageAppBuilder plus an event-feed accessor, and register both
  here so the default capabilities match what NewLambdaConnector produces when
  sync-last-activity is enabled; or (b) if the default-capabilities output cannot
  advertise it, correct docs/connector.mdx:404 to drop the "included by default on a
  brand-new connector" claim and tell users to enable the resource type manually in
  every case.

## Suggestions

In `docs/connector.mdx`:
- Around line 17-27: The Capabilities table does not list the new "GitHub Activity"
  resource type this PR enables via sync-last-activity. Add a row for it with Sync
  checked and Provision blank, plus a footnote noting it is only synced when
  sync-last-activity is enabled and requires audit-log access, linking to the
  "Sync member last activity" section.

@github-actions github-actions Bot left a comment

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.

No blocking issues found.

Also document the GitHub App permission required for sync-last-activity
alongside the existing PAT scope requirement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@JavierCarnelli-ConductorOne
JavierCarnelli-ConductorOne marked this pull request as ready for review September 15, 2026 07:49

@github-actions github-actions Bot left a comment

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.

Blocking issues found — see review comments.

@JavierCarnelli-ConductorOne
JavierCarnelli-ConductorOne dismissed github-actions[bot]’s stale review September 15, 2026 08:14

Changes requested without context. I'll execute the review again

Comment thread docs/connector.mdx Outdated
@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Connector PR Review: CXP-383 Enable GitHub last-activity sync

Blocking Issues: 0 | Suggestions: 1 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 3bfad3e7ce9c.
Review mode: incremental since 862dc46
View review run

Review Summary

The new commit bumps vendored baton-github v0.4.6 → v0.4.7, which lands fixes for two previously reported findings: nextAuditLogPage now checks Response.After first and records whether the cursor belongs in After or Page (AuditLogCursorIsPage), closing the GHEC/GHES single-page truncation bug; and the "org lacks audit-log access" warning is now per-org logarithmically sampled via the new logging.go (shouldLogSample + total_occurrences), addressing the L7 noise finding. The full PR diff was re-scanned for security and correctness — no blocking issues; go.mod, go.sum, and vendor/modules.txt are consistent, and the /go.mod hash is unchanged between v0.4.6 and v0.4.7, so no transitive dependency moved. The incremental artifact dropped 3 vendored/generated sections (logging.go, usage_event_feed.go, modules.txt); those are the substance of this commit, so they were reviewed from the full diff and the local checkout rather than assumed safe.

Security Issues

None found.

Correctness Issues

None found. The After-first cursor ordering matches go-github v69's Link-header parsing (vendor/github.com/google/go-github/v69/github/github.go:744-750), and ListCursorOptions.After exists, so the fix is wired correctly.

Suggestions

  • vendor/github.com/conductorone/baton-github/pkg/connector/usage_event_feed.go:218-224 — now that pagination actually advances, an empty page carrying a rel="next" link keeps the loop paging without advancing OrgIndex; reachedBoundary cannot fire because created:>=<since> already filters server-side, so the Link header is the sole termination signal. Consider advancing to the next org when len(entries) == 0.

Previously reported, still open (no new inline comments posted):

  • pkg/connector/connector.go:45-58 — defaultCapabilitiesBuilder.ResourceSyncers still omits the usage-app syncer and the type still does not implement EventFeeds. Maintainer decision on the thread: intentionally skipped, to be enabled only on request.
  • docs/connector.mdx:79-84 — the classic-PAT "Select the following Scopes:" list still omits read:audit_log, which line 400 requires (D3). The Capabilities-table finding (D1) is now addressed: "GitHub Activity" is listed at line 28 with the *** footnote.
  • go.mod:6 — the PR description still says "v0.4.0 to v0.4.5" (and the impact heading says v0.4.6) while the branch now ships v0.4.7; it should name the shipped version and the pagination / log-sampling changes it carries.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `vendor/github.com/conductorone/baton-github/pkg/connector/usage_event_feed.go` (fix belongs upstream in baton-github):
- Around line 218-224: the per-org pagination loop only stops when `nextAuditLogPage(resp)`
  returns an empty string or `reachedBoundary` is true. `reachedBoundary` is set only when an
  entry's timestamp is at or before `since`, but the request already sends `created:>=<since>`
  as a server-side phrase, so such an entry is normally never returned. That leaves the Link
  header as the sole termination signal: a response with zero entries but a `rel="next"` link
  makes the loop `continue` on the same org indefinitely, burning the whole
  `maxAuditLogPagesPerCall` budget each call and returning `HasMore: true` without ever
  incrementing `cursor.OrgIndex`. Add a guard so that when `len(entries) == 0` the code falls
  through to the "advance to the next org" branch (reset `AuditLogCursor` and
  `AuditLogCursorIsPage`, increment `OrgIndex`) instead of following the next cursor.

In `docs/connector.mdx`:
- Around line 79-84: the classic personal-access-token "Select the following Scopes:" list
  omits `read:audit_log`, even though the "Sync member last activity" section at line 400
  requires it. Add a bullet such as "read:audit_log - required only if you enable Sync user
  last activity" so a user following the setup steps in order builds a token that can
  actually use the feature.

In the PR description (not a file):
- The summary still says the vendored `baton-github` bump is "v0.4.0 to v0.4.5" and the impact
  heading says "v0.4.0 -> v0.4.6", but the branch now ships v0.4.7. Update the description to
  name v0.4.7 and list what the later releases added: the audit-log `after=` cursor pagination
  fix and the per-org sampled warning for orgs without audit-log access.

@github-actions github-actions Bot left a comment

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.

Blocking issues found — see review comments.

The config field's display name is "Sync user last activity"
(pkg/config/config.go:81), but the docs referred to it as "Sync last
activity" in four places, which wouldn't match what users see in the
C1 config UI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

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.

Blocking issues found — see review comments.

@FeliLucero1 FeliLucero1 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Went through this focusing on the stuff authored here (the vendored v0.4.5 changes I'd raise upstream separately). Mostly docs — nothing blocking in the Go. The capabilities thread I'll leave to your reply there, but see the note on line 401, I think that one sentence is fixable regardless of how that lands.

Also minor: title says "update the sdk's version" but baton-sdk was already v0.30.0 on main — only baton-github moves. Same for the v0.26.0 → v0.30.0 line in the description.


Two leftovers in docs/connector.mdx I couldn't anchor inline (both outside the diff):

  • Line ~30 still says pending invitations show as Unspecified — this PR is what changes that to Pending (the invitation.go status change in v0.4.5). Worth updating in the same PR that breaks it.
  • Capabilities table (~17-27) doesn't have a GitHub Activity row yet — bot flagged this one too. Sync checked, Provision blank, footnote that it only syncs with sync-last-activity on.

Comment thread docs/connector.mdx Outdated
Comment thread docs/connector.mdx Outdated
Comment thread pkg/config/config.go Outdated
AppPrivatekeyPath: ghc.AppPrivatekeyPath,
Org: ghc.Org,
DirectCollaboratorsOnly: ghc.DirectCollaboratorsOnly,
SyncLastActivity: ghc.SyncLastActivity,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Not about this line specifically, but while you're in here: v0.4.5 also adds AppPrivatekey (raw PEM) upstream, and appPrivateKeyPEM() prefers it over the path. Githubenterprise never got the field, so it's always empty for this connector.

Not a regression — app-privatekey-path is still WithRequired(true) so the fallback always hits — just means GitHub App users here can't use the paste-the-PEM option that baton-github has. Intentional?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As discussed on the SU today, we will skip these and enable them only if requested

- Reword last-activity docs to reflect that attribution is global
  across all synced orgs, not per-organization
- Drop the ambiguous "included by default on brand-new connectors"
  claim for the GitHub Activity resource type
- Fix stale invitation status doc (Unspecified -> Pending, per the
  v0.4.5 vendor bump already in this PR)
- Add missing GitHub Activity row to the capabilities table
- Clarify that sync-last-activity is based on audit-log activity in
  the config field description (regenerated config_schema.json and
  README accordingly)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@JavierCarnelli-ConductorOne JavierCarnelli-ConductorOne changed the title CXP-383 Update the sdk's version and enable Usage Event Feeds CXP-383 Enable GitHub last-activity sync Sep 15, 2026

@github-actions github-actions Bot left a comment

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.

No blocking issues found.

Comment thread vendor/github.com/conductorone/baton-github/pkg/connector/usage_event_feed.go Outdated
Comment thread pkg/config/config.go
Resolved conflicts in go.mod/go.sum: kept baton-github v0.4.5 (this
PR's change) and took baton-sdk v0.30.1 (main's newer patch bump,
unrelated to this PR). Regenerated go.sum and vendor/ via
`go mod tidy && go mod vendor` against the resolved go.mod; verified
config_schema.json and baton_capabilities.json need no changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment on lines +249 to +257
func nextAuditLogPage(resp *github.Response) string {
if resp.NextPageToken != "" {
return resp.NextPageToken
}
if resp.NextPage != 0 {
return strconv.Itoa(resp.NextPage)
}
return ""
}

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.

🟠 Bug: nextAuditLogPage never finds the audit-log cursor on github.com/GHEC, so each org is truncated to one page (100 entries) per pass. GitHub's audit-log Link header uses after=<cursor> with no page= param, and go-github puts that value in Response.After — see its own doc comment: "For APIs that support before/after pagination, such as OrganizationsService.AuditLog. Before string / After string" (vendor/github.com/google/go-github/v69/github/github.go:672-674). In populatePageValues (same file, 731-750) page is "", so strconv.Atoi("") fails → NextPageToken = "" and NextPage = 0, while r.After gets the cursor. Both branches here return "", reachedBoundary is false, and the loop advances to the next org — silently dropping every entry past the first page, so members whose only recent activity falls beyond it get no last-activity date.

The request side has the matching half of the bug: the cursor is fed into ListCursorOptions.Page (line 153), but this endpoint expects ListCursorOptions.After. Fix: return resp.After (keeping the NextPageToken/NextPage fallbacks for GHES) and set opts.After from the stored cursor instead of opts.Page. Needs to land upstream in baton-github.

Comment on lines +181 to +184
case isNotFoundError(resp) || isPermissionError(resp):
l.Warn("org lacks audit-log access, skipping it for this pass",
zap.String("org", orgName), zap.Error(err))
entries, resp = nil, nil

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.

🟡 Suggestion: an org that permanently lacks audit-log access (no read:audit_log, or a plan without audit log) is re-probed and re-warned on every pass, and the feed polls continuously rather than once per sync. With several such orgs that is a steady stream of Warn lines plus one wasted API call per org per poll, indefinitely. Consider caching the known-inaccessible orgs on usageEventFeed (or sampling the warning per criteria L7) so the condition is reported once rather than every pass. Upstream fix in baton-github.

Comment thread go.mod Outdated

require (
github.com/conductorone/baton-github v0.4.0
github.com/conductorone/baton-github v0.4.6

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.

🟡 Suggestion: the bump lands on v0.4.6, but the PR description says "v0.4.0 to v0.4.5" and its "Dependency bump impact" list only covers baton-github #184 and #181. The vendored diff also carries an unrelated, undescribed change set: a new app-privatekey config field, removal of WithRequired(true) from app-privatekey-path (now enforced at runtime in appPrivateKeyPEM), and \n-unescaping in loadPrivateKeyFromString. None of that affects this connector — pkg/config/config.go keeps its own required app-privatekey-path and NewLambdaConnector never sets AppPrivatekey — but please update the description so the review and release notes match what actually shipped.

@github-actions github-actions Bot left a comment

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.

Blocking issues found — see review comments.

@mateoHernandez123 mateoHernandez123 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approving. The GHES-style truncation we flagged is addressed by the baton-github v0.4.6 bump (#192): nextAuditLogPage now follows numeric NextPage, which is the Link shape this connector's instance-URL / GHES path actually sees.

The built-in C1 schema finding was incorrect for a deployment-supported lambda — the field ships with the connector release schema, no hand-edit of the C1 config contract.

Leftover, not blocking here: github.com/GHEC after= cursors still land in resp.After and are still sent as ListCursorOptions.Page. This connector's docs send github.com users to the GitHub integration, so that belongs in baton-github, not this PR.

Picks up the audit-log pagination fix (resp.After checked first for
the org audit-log endpoint's cursor, fixing silent truncation on
GHEC and most GHES orgs) and per-org sampled warnings for orgs that
permanently lack audit-log access (baton-github#193).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment on lines +218 to +224
if resp != nil && !reachedBoundary {
if nextPage, isPage := nextAuditLogPage(resp); nextPage != "" {
cursor.AuditLogCursor = nextPage
cursor.AuditLogCursorIsPage = isPage
continue
}
}

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.

🟡 Suggestion: now that after= cursors actually advance, an empty page with a rel="next" link keeps the loop paging without ever advancing cursor.OrgIndex. reachedBoundary can only fire when an entry is at/before since, but created:>=<since> already filters those out server-side, so the only termination signal for an org is nextAuditLogPage returning "". Consider also advancing to the next org when len(entries) == 0, so a server that keeps emitting a next-link can't stall the pass. (Medium confidence — bounded per call by maxAuditLogPagesPerCall, but the stall would persist across polls.)

@github-actions github-actions Bot left a comment

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.

No blocking issues found.

@JavierCarnelli-ConductorOne
JavierCarnelli-ConductorOne merged commit b800961 into main Sep 18, 2026
11 checks passed
@JavierCarnelli-ConductorOne
JavierCarnelli-ConductorOne deleted the feat/cxp-383 branch September 18, 2026 17:05
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.

5 participants