diff --git a/changelog/unreleased/workbuddy-checkin-timezone-and-errors.md b/changelog/unreleased/workbuddy-checkin-timezone-and-errors.md new file mode 100644 index 0000000..ac78893 --- /dev/null +++ b/changelog/unreleased/workbuddy-checkin-timezone-and-errors.md @@ -0,0 +1,9 @@ +### English + +- WorkBuddy CN automatic check-in now reads the configured time in Asia/Shanghai instead of the container's local timezone. The supported Docker Compose install sets no `TZ`, so the time was previously treated as UTC and fired eight hours off the intended local time. +- A dropped connection or timeout during the WorkBuddy daily check-in is now reported as a transport error instead of `checkin parse: unexpected end of JSON input`. + +### 中文 + +- WorkBuddy CN 的自动签到改为按 Asia/Shanghai 解释配置的时间,不再跟随容器本地时区。官方 Docker Compose 部署未设置 `TZ`,此前该时间被当作 UTC 处理,实际触发时刻比预期偏差八小时。 +- WorkBuddy 每日签到遇到连接中断或超时时,现在会如实报告传输层错误,不再显示 `checkin parse: unexpected end of JSON input`。 diff --git a/internal/providers/registry.go b/internal/providers/registry.go index 05a8d5a..881b326 100644 --- a/internal/providers/registry.go +++ b/internal/providers/registry.go @@ -130,7 +130,7 @@ var WorkBuddy = ProviderDescriptor{ ID: "cn", Label: "CN", ChatBase: "https://copilot.tencent.com", BillingBase: "https://www.codebuddy.cn", AuthBase: "https://copilot.tencent.com", DefaultDomain: "codebuddy.cn", - Checkin: &CheckinPolicy{Timezone: "Local"}, + Checkin: &CheckinPolicy{Timezone: "Asia/Shanghai"}, }, { ID: "global", Label: "Global", ChatBase: "https://www.workbuddy.ai", diff --git a/internal/providers/workbuddy/client.go b/internal/providers/workbuddy/client.go index 0ae2889..8b7303d 100644 --- a/internal/providers/workbuddy/client.go +++ b/internal/providers/workbuddy/client.go @@ -830,6 +830,9 @@ func (c *Client) DailyCheckin(ctx context.Context, accountID string) (string, er break } } + if err != nil { + return "", err + } text := strings.TrimSpace(string(body)) classified := Classify(status, text) if classified.Kind == accounts.KindAuth { diff --git a/internal/providers/workbuddy/client_test.go b/internal/providers/workbuddy/client_test.go index 0c0169e..a46329f 100644 --- a/internal/providers/workbuddy/client_test.go +++ b/internal/providers/workbuddy/client_test.go @@ -1567,6 +1567,38 @@ func TestDailyCheckinAlreadyCheckedInHTTP400(t *testing.T) { } } +func TestDailyCheckinSurfacesTransportError(t *testing.T) { + originalDelays := dailyCheckinRetryDelays + dailyCheckinRetryDelays = []time.Duration{0, 0} + t.Cleanup(func() { dailyCheckinRetryDelays = originalDelays }) + + client, store := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + hijacker, ok := w.(http.Hijacker) + if !ok { + t.Errorf("response writer is not a hijacker") + return + } + conn, _, err := hijacker.Hijack() + if err != nil { + t.Errorf("hijack: %v", err) + return + } + _ = conn.Close() + })) + payload, _ := json.Marshal(Credential{ + AccessToken: "at", RefreshToken: "rt", ExpiresAt: 4102444800, Domain: DomainCN, UID: "u1", + }) + _ = store.SaveCredentialPayload(context.Background(), "acc1", CredentialFormat, payload) + + _, err := client.DailyCheckin(context.Background(), "acc1") + if err == nil { + t.Fatal("a dropped connection must fail the check-in") + } + if strings.Contains(err.Error(), "checkin parse") { + t.Fatalf("transport error was reported as a parse error: %v", err) + } +} + func TestDailyCheckinRetriesTransientFailures(t *testing.T) { var calls atomic.Int32 client, store := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {