Report repository import storage failures

41a86558dc9c · AtlantisPleb · · parent 9b4fa727f63a

Report repository import storage failures

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • added lib/openagents/repositories/git_failure.ex
  • modified lib/openagents/repositories/importer.ex
  • added test/openagents/repositories/git_failure_test.exs

Diff

3 files changed, +78 -5

lib/openagents/repositories/git_failure.ex added +27

@@ -0,0 +1,27 @@

1
defmodule OpenAgents.Repositories.GitFailure do
2
  @moduledoc false
3
4
  @insufficient_storage_markers [
5
    "no space left on device",
6
    "disk quota exceeded"
7
  ]
8
9
  @temporary_storage_markers [
10
    "permission denied",
11
    "read-only file system"
12
  ]
13
14
  def classify(output, fallback) when is_binary(output) and is_atom(fallback) do
15
    normalized = String.downcase(output)
16
17
    cond do
18
      contains_any?(normalized, @insufficient_storage_markers) -> :insufficient_storage
19
      contains_any?(normalized, @temporary_storage_markers) -> :temporary_storage_unavailable
20
      true -> fallback
21
    end
22
  end
23
24
  def classify(_output, fallback) when is_atom(fallback), do: fallback
25
26
  defp contains_any?(output, markers), do: Enum.any?(markers, &String.contains?(output, &1))
27
end
lib/openagents/repositories/importer.ex modified +23 -5

@@ -5,7 +5,7 @@ defmodule OpenAgents.Repositories.Importer do

5 5
6 6
  alias OpenAgents.{Accounts, Audit, GitHubOAuth, Repo}
7 7
  alias OpenAgents.Forge.{Repos, Sync, WAL}
8
  alias OpenAgents.Repositories.{Repository, RepositoryImport}
8
  alias OpenAgents.Repositories.{GitFailure, Repository, RepositoryImport}
9 9
10 10
  @maximum_append_attempts 3
11 11
  @default_import_timeout_ms 6 * 60 * 60 * 1_000

@@ -220,8 +220,13 @@ defmodule OpenAgents.Repositories.Importer do

220 220
      ]
221 221
222 222
      case git_runner.(source_repository, args, env: environment) do
223
        {_output, 0} -> :ok
224
        {_output, _status} -> {:error, :source_fetch_failed}
223
        {_output, 0} ->
224
          :ok
225
226
        {output, status} ->
227
          reason = GitFailure.classify(output, :source_fetch_failed)
228
          log_git_failure("fetch_source", status, reason)
229
          {:error, reason}
225 230
      end
226 231
    end
227 232
  end

@@ -319,8 +324,10 @@ defmodule OpenAgents.Repositories.Importer do

319 324
            {:error, :bundle_unavailable}
320 325
        end
321 326
322
      {_output, _status} ->
323
        {:error, :bundle_creation_failed}
327
      {output, status} ->
328
        reason = GitFailure.classify(output, :bundle_creation_failed)
329
        log_git_failure("create_payload", status, reason)
330
        {:error, reason}
324 331
    end
325 332
  end
326 333

@@ -532,6 +539,8 @@ defmodule OpenAgents.Repositories.Importer do

532 539
  defp error_code(:github_token_missing), do: "github_connection_required"
533 540
  defp error_code(:import_timeout), do: "import_timeout"
534 541
  defp error_code(:import_too_large), do: "import_too_large"
542
  defp error_code(:insufficient_storage), do: "insufficient_storage"
543
  defp error_code(:temporary_storage_unavailable), do: "temporary_storage_unavailable"
535 544
  defp error_code(_reason), do: "import_failed"
536 545
537 546
  defp import_stage(repository, repository_import, stage, operation) do

@@ -583,6 +592,15 @@ defmodule OpenAgents.Repositories.Importer do

583 592
584 593
  defp diagnostic_error(_reason), do: " error_code=unexpected_error"
585 594
595
  defp log_git_failure(command, status, reason) do
596
    Logger.warning(
597
      "repository_import_git_failure" <>
598
        " command=#{command}" <>
599
        " exit_status=#{status}" <>
600
        " error_code=#{reason}"
601
    )
602
  end
603
586 604
  defp put_payload(storage_key, seq, {:file, path}),
587 605
    do: WAL.put_entry_file(storage_key, seq, path)
588 606
test/openagents/repositories/git_failure_test.exs added +28

@@ -0,0 +1,28 @@

1
defmodule OpenAgents.Repositories.GitFailureTest do
2
  use ExUnit.Case, async: true
3
4
  alias OpenAgents.Repositories.GitFailure
5
6
  test "classifies exhausted storage without exposing command output" do
7
    assert GitFailure.classify(
8
             "fatal: cannot create snapshot.bundle: No space left on device",
9
             :bundle_creation_failed
10
           ) == :insufficient_storage
11
12
    assert GitFailure.classify("error: Disk quota exceeded", :source_fetch_failed) ==
13
             :insufficient_storage
14
  end
15
16
  test "classifies an unavailable temporary filesystem" do
17
    assert GitFailure.classify("fatal: Permission denied", :bundle_creation_failed) ==
18
             :temporary_storage_unavailable
19
20
    assert GitFailure.classify("fatal: Read-only file system", :source_fetch_failed) ==
21
             :temporary_storage_unavailable
22
  end
23
24
  test "preserves the operation-specific fallback" do
25
    assert GitFailure.classify("fatal: malformed object", :bundle_creation_failed) ==
26
             :bundle_creation_failed
27
  end
28
end

This page updates live while a promote is in flight · changelog