fix(agent): fallback to session/new when auggie session not found (#3911)

This commit is contained in:
Carlos Florêncio
2026-09-24 19:08:35 +01:00
committed by GitHub
parent 200af92fbe
commit 6676d46175
3 changed files with 64 additions and 14 deletions
@@ -251,7 +251,7 @@ func (sm *SessionManager) createOrLoadSession(
zap.Bool("method_not_found", isMethodNotFoundErr(err)),
zap.Bool("capability_mismatch", hasCanonicalSessionLoadMessage(err, "agent does not support session loading (LoadSession capability is false)")),
zap.Bool("session_unknown", isSessionUnknownErr(err)),
zap.Bool("provider_session_missing", isMissingProviderRolloutErr(err, existingSessionID)))
zap.Bool("provider_session_missing", isMissingProviderSessionErr(err, existingSessionID)))
return sm.createNewSession(ctx, client, agentConfig, workspacePath, mcpServers)
}
return sm.createNewSession(ctx, client, agentConfig, workspacePath, mcpServers)
@@ -1905,7 +1905,7 @@ func isSessionLoadFallbackErr(err error, expectedSessionID string) bool {
return false
}
if isMethodNotFoundErr(err) || isSessionUnknownErr(err) ||
isMissingProviderRolloutErr(err, expectedSessionID) {
isMissingProviderSessionErr(err, expectedSessionID) {
return true
}
return hasCanonicalSessionLoadMessage(err, "Method not found") ||
@@ -1915,7 +1915,9 @@ func isSessionLoadFallbackErr(err error, expectedSessionID string) bool {
const (
jsonRPCInternalError = -32603
jsonRPCInvalidParams = -32602
missingProviderRolloutPrefix = "no rollout found for thread id "
missingProviderSessionPrefix = "Session not found: "
)
type sessionLoadRequestError struct {
@@ -1926,24 +1928,25 @@ type sessionLoadRequestError struct {
} `json:"data"`
}
// isMissingProviderRolloutErr recognizes Codex's explicit not-found response
// after its process-local rollout state disappeared. The session ID must match
// the one Kandev attempted to load; unrelated internal errors remain fatal.
func isMissingProviderRolloutErr(err error, expectedSessionID string) bool {
// isMissingProviderSessionErr recognizes Codex's explicit not-found response
// after its process-local rollout state disappeared and Auggie's session not
// found error. The session ID must match the one Kandev attempted to load;
// unrelated internal errors remain fatal.
func isMissingProviderSessionErr(err error, expectedSessionID string) bool {
if err == nil || strings.TrimSpace(expectedSessionID) == "" {
return false
}
var reqErr *acp.RequestError
if errors.As(err, &reqErr) {
encoded, marshalErr := json.Marshal(reqErr)
if marshalErr == nil && matchesMissingProviderRollout(encoded, expectedSessionID) {
if marshalErr == nil && matchesMissingProviderSession(encoded, expectedSessionID) {
return true
}
}
message := err.Error()
for offset := strings.IndexByte(message, '{'); offset >= 0; {
candidate := message[offset:]
if matchesMissingProviderRollout([]byte(candidate), expectedSessionID) {
if matchesMissingProviderSession([]byte(candidate), expectedSessionID) {
return true
}
next := strings.IndexByte(candidate[1:], '{')
@@ -1955,14 +1958,22 @@ func isMissingProviderRolloutErr(err error, expectedSessionID string) bool {
return false
}
func matchesMissingProviderRollout(encoded []byte, expectedSessionID string) bool {
func matchesMissingProviderSession(encoded []byte, expectedSessionID string) bool {
var projected sessionLoadRequestError
if err := json.Unmarshal(encoded, &projected); err != nil {
return false
}
return projected.Code == jsonRPCInternalError &&
if projected.Code == jsonRPCInternalError &&
projected.Message == "Internal error" &&
projected.Data.Details == missingProviderRolloutPrefix+expectedSessionID
projected.Data.Details == missingProviderRolloutPrefix+expectedSessionID {
return true
}
if projected.Code == jsonRPCInvalidParams &&
projected.Message == "Invalid params" &&
projected.Data.Details == missingProviderSessionPrefix+expectedSessionID {
return true
}
return false
}
func hasCanonicalSessionLoadMessage(err error, canonical string) bool {
@@ -120,6 +120,11 @@ func TestInitializeSession_LoadCompatibilityFailureCreatesReplacement(t *testing
message: `load session failed: failed to load session: {"code":-32603,"message":"Internal error",` +
`"data":{"details":"no rollout found for thread id saved-session"}}`,
},
{
name: "missing auggie session",
message: `load session failed: failed to load session: {"code":-32602,"message":"Invalid params",` +
`"data":{"details":"Session not found: saved-session"}}`,
},
}
for _, tt := range tests {
@@ -8,7 +8,7 @@ import (
"github.com/coder/acp-go-sdk"
)
func TestIsMissingProviderRolloutErrRequiresMatchingStructuredEvidence(t *testing.T) {
func TestIsMissingProviderSessionErrRequiresMatchingStructuredEvidence(t *testing.T) {
tests := []struct {
name string
err error
@@ -55,12 +55,46 @@ func TestIsMissingProviderRolloutErrRequiresMatchingStructuredEvidence(t *testin
`{"code":-32603,"message":"Internal error","data":{"details":"no rollout found for thread id saved-session"}} trailing`,
),
},
{
name: "matching auggie session not found error",
err: fmt.Errorf("load failed: %w", &acp.RequestError{
Code: -32602,
Message: "Invalid params",
Data: map[string]any{
"details": "Session not found: saved-session",
},
}),
want: true,
},
{
name: "matching auggie session not found in json string",
err: errors.New(
`load session failed: failed to load session: {"code":-32602,"message":"Invalid params","data":{"details":"Session not found: saved-session"}}`,
),
want: true,
},
{
name: "auggie session not found for different session",
err: &acp.RequestError{
Code: -32602,
Message: "Invalid params",
Data: map[string]any{"details": "Session not found: other-session"},
},
},
{
name: "auggie session not found with case mismatch",
err: &acp.RequestError{
Code: -32602,
Message: "Invalid params",
Data: map[string]any{"details": "Session not found: SAVED-SESSION"},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := isMissingProviderRolloutErr(test.err, "saved-session"); got != test.want {
t.Fatalf("isMissingProviderRolloutErr() = %t, want %t", got, test.want)
if got := isMissingProviderSessionErr(test.err, "saved-session"); got != test.want {
t.Fatalf("isMissingProviderSessionErr() = %t, want %t", got, test.want)
}
})
}