Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/fixed-18107-claude-rebridge-selfreport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Completed Claude credential re-bridges (a token refresh replacing an agent's shared-login symlink, repaired automatically) no longer file `unclassified_error_repeated` fleet self-reports; failed repairs still do.
3 changes: 3 additions & 0 deletions src/crux/agent/claude_credential_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,9 @@ func (m *Manager) rebridgeClaudeCredential(agentName, link, target, trigger stri
if rep == nil {
return nil
}
// The fleet self-report feed deliberately ignores this exact message (a
// completed repair, expected in a burst at every token refresh):
// claudeCredentialRebridged in pkg/fleetreport/slogfeed. Keep them in sync.
m.logger.Warn("claude credential bridge: a private credential file had replaced the shared-login symlink; re-bridged",
"agent", agentName,
"trigger", trigger,
Expand Down
19 changes: 18 additions & 1 deletion src/pkg/fleetreport/slogfeed/slogfeed.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func Key(msg string) string {
}

func (h *Handler) feed(r slog.Record) {
if canceledError(r, h.attrs) || expectedKickHold(r) || rateLimitedError(r) || sentinelPacingSkip(r) || upstreamUnavailableError(r) || failClosedStatus500(r) || hubHeartbeatUnavailable(r) || hubHeartbeatCachedFallback(r) || reviewCommentCapped(r) || inferenceBlockedStall(r) || automergeMergeGuardSkip(r) || reviewRetryServerError(r) || stageDampenerSuppression(r) || signedCommitOwnershipSkip(r) {
if canceledError(r, h.attrs) || expectedKickHold(r) || rateLimitedError(r) || sentinelPacingSkip(r) || upstreamUnavailableError(r) || failClosedStatus500(r) || hubHeartbeatUnavailable(r) || hubHeartbeatCachedFallback(r) || reviewCommentCapped(r) || inferenceBlockedStall(r) || automergeMergeGuardSkip(r) || reviewRetryServerError(r) || stageDampenerSuppression(r) || signedCommitOwnershipSkip(r) || claudeCredentialRebridged(r) {
return
}
class := fleetreport.ClassUnclassifiedRepeated
Expand Down Expand Up @@ -143,6 +143,23 @@ func signedCommitOwnershipSkip(r slog.Record) bool {
return r.Level == slog.LevelWarn && strings.TrimSpace(r.Message) == "signed-commit reconciler: skipped; the PR keeps an unsigned commit"
}

// claudeCredentialRebridgedMessage is the WARN crux/agent logs after it has
// SUCCESSFULLY restored a per-agent Claude credential symlink that a token
// refresh replaced (src/crux/agent/claude_credential_bridge.go). Keep the two
// strings in sync.
const claudeCredentialRebridgedMessage = "claude credential bridge: a private credential file had replaced the shared-login symlink; re-bridged"

// claudeCredentialRebridged is the credential bridge's completed repair. Claude
// Code renames a fresh file over the shared-login symlink on every token
// refresh, every agent shares one access token and so refreshes within the same
// minute or two, and the watchdog checks each link every 5s: a burst of one
// re-bridge per agent is the designed recovery, already recorded in the audit
// log, not a defect (hivecommons/hive#18107). The WARN stays in local logs; a
// failed or refused repair logs a different message and stays reportable.
func claudeCredentialRebridged(r slog.Record) bool {
return r.Level == slog.LevelWarn && strings.TrimSpace(r.Message) == claudeCredentialRebridgedMessage
}

// canceledError applies the existing cancellation exclusion to structured
// errors too. An accompanying different error must remain reportable.
func canceledError(r slog.Record, bound []slog.Attr) bool {
Expand Down
18 changes: 18 additions & 0 deletions src/pkg/fleetreport/slogfeed/slogfeed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,24 @@ func TestSignedCommitOwnershipSkipStaysInLogsWithoutFleetReport(t *testing.T) {
}
}

func TestClaudeCredentialRebridgeStaysInLogsWithoutFleetReport(t *testing.T) {
logger, seen, out := newFeed(t)
for range 6 {
logger.Warn(claudeCredentialRebridgedMessage, "agent", "reviewer", "trigger", "watchdog", "kept", "shared")
}
if len(*seen) != 0 {
t.Fatalf("completed re-bridges produced observations: %+v", *seen)
}
if n := strings.Count(out.String(), "re-bridged"); n != 6 {
t.Fatalf("local logs lost the re-bridge warning: got %d, want 6: %s", n, out.String())
}
logger.Warn("claude credential bridge: repair failed", "agent", "reviewer", "error", "restore credential symlink: file exists")
logger.Error(claudeCredentialRebridgedMessage, "agent", "reviewer")
if len(*seen) != 2 || (*seen)[0].class != fleetreport.ClassUnclassifiedRepeated || (*seen)[1].class != fleetreport.ClassUnclassifiedRepeated {
t.Fatalf("failed repairs must remain reportable: %+v", *seen)
}
}

func TestCanceledStageDigestStaysLocal(t *testing.T) {
logger, seen, out := newFeed(t)
const msg = "stage labels: check digest failed"
Expand Down
Loading