{"record":{"id":"7a36bfa2a619e731","repo":"juicedata/juicefs","slug":"invalid-type-t","errorCode":null,"errorMessage":"invalid type: %T","messagePattern":"invalid type: %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/meta/sql.go","lineNumber":836,"sourceCode":"}\n\nfunc (m *dbMeta) getSession(row interface{}, detail bool) (*Session, error) {\n\tvar s Session\n\tvar info []byte\n\tswitch row := row.(type) {\n\tcase *session2:\n\t\ts.Sid = row.Sid\n\t\ts.Expire = time.Unix(row.Expire, 0)\n\t\tinfo = row.Info\n\tcase *session:\n\t\ts.Sid = row.Sid\n\t\ts.Expire = time.Unix(row.Heartbeat, 0).Add(time.Minute * 5)\n\t\tinfo = row.Info\n\t\tif info == nil { // legacy client has no info\n\t\t\tinfo = []byte(\"{}\")\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid type: %T\", row)\n\t}\n\tif err := json.Unmarshal(info, &s); err != nil {\n\t\treturn nil, fmt.Errorf(\"corrupted session info; json error: %s\", err)\n\t}\n\tif detail {\n\t\tvar (\n\t\t\tsrows []sustained\n\t\t\tfrows []flock\n\t\t\tprows []plock\n\t\t)\n\t\terr := m.roTxn(Background(), func(ses *xorm.Session) error {\n\t\t\tif err := ses.Find(&srows, &sustained{Sid: s.Sid}); err != nil {\n\t\t\t\treturn fmt.Errorf(\"find sustained %d: %s\", s.Sid, err)\n\t\t\t}\n\t\t\ts.Sustained = make([]Ino, 0, len(srows))\n\t\t\tfor _, srow := range srows {\n\t\t\t\ts.Sustained = append(s.Sustained, srow.Inode)\n\t\t\t}","sourceCodeStart":818,"sourceCodeEnd":854,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/pkg/meta/sql.go#L818-L854","documentation":"Thrown by dbMeta.getSession when the row passed to it is neither *session2 (current schema) nor *session (legacy schema). This is an internal invariant: GetSession/FindSession only ever construct those two row types, so this branch should be unreachable. It surfaces only if internal code evolves inconsistently (a new row type added without extending the switch).","triggerScenarios":"A developer calls getSession with a row type other than *session2 or *session — practically only possible when modifying pkg/meta/sql.go and adding a new session table/row type without updating the switch. Not triggerable by normal API users.","commonSituations":"During development of a new metadata schema variant; during a custom fork or patch of the SQL engine; never in production for stock JuiceFS.","solutions":["Inspect the %T in the message to find which row type leaked into getSession.","Add a case for that row type in the switch in getSession, mapping Sid/Expire/Info.","If seen in stock JuiceFS, file a bug — it indicates an internal code path inconsistency."],"exampleFix":"// before (internal code passes an unmapped row type)\nreturn nil, fmt.Errorf(\"invalid type: %T\", row)\n// after: extend the switch\nswitch row := row.(type) {\ncase *session2: ...\ncase *session: ...\ncase *myNewSessionRow:\n    s.Sid = row.Sid\n    s.Expire = time.Unix(row.Expire, 0)\n    info = row.Info\n}","handlingStrategy":"type-guard","validationCode":"// assert the row type before calling internal getSession\nswitch row.(type) {\ncase *meta.Session2, *meta.LegacySession:\n    // ok\ndefault:\n    log.Fatalf(\"unsupported session row type %T\", row)\n}","typeGuard":"func validSessionRow(row interface{}) bool {\n    switch row.(type) {\n    case *session2, *session:\n        return true\n    }\n    return false\n}","tryCatchPattern":"if err != nil {\n    if strings.Contains(err.Error(), \"invalid type: \") {\n        logger.Fatalf(\"internal bug: unmapped session row type — report to maintainers\")\n    }\n}","preventionTips":["When adding a new session table variant, update every switch over session row types in one change.","Cover getSession with unit tests for all row types."],"tags":["type-mismatch","metadata","session","go"],"backgroundTag":"type-mismatch","analyzedSha":"c9a67b23e8e08ec23ec331aa6f1675e2319e921c","analyzedAt":"2026-09-06T17:55:48.476Z","contentChangedAt":"2026-09-06T17:55:48.476Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}