fix(agent): fallback to session/new when auggie session not found (#3911)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user