{"record":{"id":"4bb1a62803829610","repo":"googleapis/mcp-toolbox","slug":"error-generating-logs-url-v","errorCode":null,"errorMessage":"error generating logs url: %v","messagePattern":"error generating logs url: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/sources/dataproc/dataproc.go","lineNumber":331,"sourceCode":"\t\treturn nil, fmt.Errorf(\"failed to list jobs: %w\", err)\n\t}\n\n\tjobs, err := ToJobs(jobPbs, s.Region)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ListJobsResponse{Jobs: jobs, NextPageToken: nextPageToken}, nil\n}\n\n// ToJobs converts a slice of protobuf Job messages to a slice of Job structs.\nfunc ToJobs(jobPbs []*dataprocpb.Job, region string) ([]Job, error) {\n\tjobs := make([]Job, 0, len(jobPbs))\n\tfor _, jobPb := range jobPbs {\n\t\tconsoleUrl := JobConsoleURLFromProto(jobPb, region)\n\t\tlogsUrl, err := JobLogsURLFromProto(jobPb, region)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error generating logs url: %v\", err)\n\t\t}\n\n\t\tstatus := \"STATE_UNSPECIFIED\"\n\t\tsubStatus := \"\"\n\t\tvar startTime, endTime string\n\n\t\tif jobPb.Status != nil {\n\t\t\tstatus = jobPb.Status.State.Enum().String()\n\t\t\tsubStatus = jobPb.Status.Substate.Enum().String()\n\t\t}\n\n\t\tvar sTime, eTime time.Time\n\t\tfor _, s := range jobPb.StatusHistory {\n\t\t\tt := s.StateStartTime.AsTime()\n\t\t\tif sTime.IsZero() || t.Before(sTime) {\n\t\t\t\tsTime = t\n\t\t\t}\n\t\t}","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/sources/dataproc/dataproc.go#L313-L349","documentation":"ToJobs builds a Cloud Console logs URL for each job via JobLogsURLFromProto; when that helper errors (typically because the job proto lacks placement info), ToJobs aborts the whole conversion and returns this wrapped message with %v. Note it uses %v, so the original error is not unwrap-able programmatically.","triggerScenarios":"Calling ListJobs (which calls ToJobs) when any returned job's dataprocpb.Job has a nil Placement field, so JobLogsURLFromProto cannot determine the cluster name for the logs URL.","commonSituations":"Jobs in unusual states or created through paths that omit placement metadata; partially deleted clusters; jobs returned by filters where placement was never populated.","solutions":["Check the job proto: ensure jobs have Placement.ClusterName set (query a specific job via GetJob to inspect)","If you control job creation, verify jobs are submitted against a cluster so placement is recorded","Handle the error per-job by falling back to a URL-less result instead of failing the whole list","Report upstream if the Dataproc API legitimately returns jobs without placement"],"exampleFix":"// before\nlogsUrl, err := JobLogsURLFromProto(jobPb, region)\nif err != nil {\n\treturn nil, fmt.Errorf(\"error generating logs url: %v\", err)\n}\n// after\nlogsUrl, err := JobLogsURLFromProto(jobPb, region)\nif err != nil {\n\tlogsUrl = \"\" // degrade gracefully instead of failing the whole batch\n}","handlingStrategy":"fallback","validationCode":"if job.GetPlacement() == nil {\n\t// skip URL generation for this job\n}","typeGuard":"func hasPlacement(j *dataprocpb.Job) bool { return j != nil && j.GetPlacement() != nil }","tryCatchPattern":"logsUrl, err := JobLogsURLFromProto(jobPb, region)\nif err != nil {\n\tlogsUrl = \"\" // fall back to empty URL rather than failing the batch\n}","preventionTips":["Check job.Placement before requesting a logs URL","Handle URL generation per-job instead of failing the whole list","Verify jobs were submitted against a cluster so placement is populated"],"tags":["gcp","dataproc","url-generation","missing-metadata"],"backgroundTag":"missing-metadata-field","analyzedSha":"8cc6e09de2ad7b8bffc77751799585a1401a48eb","analyzedAt":"2026-09-05T01:10:36.887Z","contentChangedAt":"2026-09-05T01:10:36.887Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}