Skip to content

Commit 0453cf5

Browse files
committed
file name fix
1 parent 0ef8938 commit 0453cf5

2 files changed

Lines changed: 85 additions & 2 deletions

File tree

cli/beacon-hooks/internal/cloudshuttle/shuttle.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ type state struct {
7272
LastUpload string `json:"last_upload,omitempty"`
7373
LastSize int64 `json:"last_size,omitempty"`
7474
LastObject string `json:"last_object,omitempty"`
75+
Provider string `json:"provider,omitempty"`
76+
RunID string `json:"run_id,omitempty"`
7577
}
7678

7779
type tokenResponse struct {
@@ -158,17 +160,40 @@ func Upload(ctx context.Context, cfg Config, force bool) error {
158160
return err
159161
}
160162
defer cleanup()
161-
objectName := ObjectName(cfg)
163+
objectName := uploadObjectName(cfg)
162164
if err := uploadSnapshot(ctx, cfg, objectName, snapshot); err != nil {
163165
return err
164166
}
165167
return writeState(cfg.StatePath, state{
166168
LastUpload: time.Now().UTC().Format(time.RFC3339),
167169
LastSize: info.Size(),
168170
LastObject: objectName,
171+
Provider: cfg.Provider,
172+
RunID: cfg.RunID,
169173
})
170174
}
171175

176+
func uploadObjectName(cfg Config) string {
177+
st, err := readState(cfg.StatePath)
178+
if err == nil && stateObjectMatchesConfig(st, cfg) {
179+
return strings.TrimSpace(st.LastObject)
180+
}
181+
return ObjectName(cfg)
182+
}
183+
184+
func stateObjectMatchesConfig(st state, cfg Config) bool {
185+
objectName := strings.TrimSpace(st.LastObject)
186+
if objectName == "" {
187+
return false
188+
}
189+
if st.Provider != "" || st.RunID != "" {
190+
return st.Provider == cfg.Provider && st.RunID == cfg.RunID
191+
}
192+
provider := cleanKeyPart(defaultString(cfg.Provider, "unknown"))
193+
runID := cleanKeyPart(defaultString(cfg.RunID, "unknown"))
194+
return strings.HasSuffix(path.Base(objectName), "-"+provider+"-"+runID+".jsonl.gz")
195+
}
196+
172197
func ObjectName(cfg Config) string {
173198
now := nowUTC()
174199
parts := cleanKeyParts(cfg.Prefix)

cli/beacon-hooks/internal/cloudshuttle/shuttle_test.go

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,59 @@ func TestUploadSendsJSONLToS3(t *testing.T) {
270270
}
271271
}
272272

273+
func TestUploadReusesLastObjectForSameRun(t *testing.T) {
274+
current := time.Date(2026, 7, 12, 20, 17, 3, 0, time.UTC)
275+
restore := stubNowFunc(t, func() time.Time { return current })
276+
defer restore()
277+
var uploadedPaths []string
278+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
279+
uploadedPaths = append(uploadedPaths, r.URL.EscapedPath())
280+
w.WriteHeader(http.StatusOK)
281+
}))
282+
defer server.Close()
283+
284+
dir := t.TempDir()
285+
logPath := filepath.Join(dir, "runtime.jsonl")
286+
if err := os.WriteFile(logPath, []byte("{\"event\":\"first\"}\n"), 0644); err != nil {
287+
t.Fatalf("write log: %v", err)
288+
}
289+
cfg := Config{
290+
Upload: uploadS3,
291+
LogPath: logPath,
292+
StatePath: filepath.Join(dir, "state.json"),
293+
Bucket: "bucket",
294+
Prefix: "prefix",
295+
Provider: "cursor_cloud",
296+
RunID: "run",
297+
S3Region: "us-west-2",
298+
S3AccessKeyID: "AKIATEST",
299+
S3SecretKey: "secret",
300+
S3Endpoint: server.URL,
301+
}
302+
if err := Upload(context.Background(), cfg, true); err != nil {
303+
t.Fatalf("first Upload returned error: %v", err)
304+
}
305+
306+
current = current.Add(2 * time.Minute)
307+
if err := os.WriteFile(logPath, []byte("{\"event\":\"second\"}\n"), 0644); err != nil {
308+
t.Fatalf("rewrite log: %v", err)
309+
}
310+
if err := Upload(context.Background(), cfg, true); err != nil {
311+
t.Fatalf("second Upload returned error: %v", err)
312+
}
313+
314+
if len(uploadedPaths) != 2 {
315+
t.Fatalf("uploaded paths = %v, want 2 uploads", uploadedPaths)
316+
}
317+
if uploadedPaths[0] != uploadedPaths[1] {
318+
t.Fatalf("Upload used different object keys: first=%q second=%q", uploadedPaths[0], uploadedPaths[1])
319+
}
320+
want := "/bucket/prefix/runtime/date=2026-07-12/1783887423-cursor_cloud-run.jsonl.gz"
321+
if uploadedPaths[0] != want {
322+
t.Fatalf("upload path = %q, want %q", uploadedPaths[0], want)
323+
}
324+
}
325+
273326
func TestResolveRunIDUsesOnlyEnvironment(t *testing.T) {
274327
t.Setenv("CLAUDE_CODE_REMOTE_SESSION_ID", "cse_env")
275328
if got := resolveRunID(); got != "cse_env" {
@@ -302,9 +355,14 @@ func mustRSAKey(t *testing.T) *rsa.PrivateKey {
302355
}
303356

304357
func stubNow(t *testing.T, value time.Time) func() {
358+
t.Helper()
359+
return stubNowFunc(t, func() time.Time { return value.UTC() })
360+
}
361+
362+
func stubNowFunc(t *testing.T, fn func() time.Time) func() {
305363
t.Helper()
306364
old := nowUTC
307-
nowUTC = func() time.Time { return value.UTC() }
365+
nowUTC = func() time.Time { return fn().UTC() }
308366
return func() { nowUTC = old }
309367
}
310368

0 commit comments

Comments
 (0)