Skip to content

FunctionClauseError when deleting anonymous user's TagChanges batch from history #600

@Aldenar

Description

@Aldenar

Describe the bug
When attempting to remove a batch of tag changes made by an anonymous user from an images' tag change history, the request ends with a HTTP 500.

With debugging turned on, the issue at hand is a "FunctionClauseError". More in details.

Steps to reproduce the behavior:

  1. As an anonymous user, make a tag change on an image (Does not matter if adding/removing, or how many)
  2. As an administrator, go to the just modified image. Display the tag changes history, and click on the "Delete from history" button of the tag changes batch made in step 1
  3. Site returns a HTTP 500.

Expected behavior
The tag changes batch should simply be removed from history.

Screenshots
https://i.imgur.com/q3vN5AR.png

Desktop (please complete the following information):

  • OS: Win 10
  • Browser: Firefox 139.0.4 (64-bit)
  • Version (I presume philomena?): 1.2.0

Additional context

FunctionClauseError at DELETE /images/1/tag_changes/12

Exception:

** (FunctionClauseError) no function clause matching in PhilomenaWeb.Image.TagChangeController.tag_change_details/1
    (philomena 1.2.1) lib/philomena_web/controllers/image/tag_change_controller.ex:57: PhilomenaWeb.Image.TagChangeController.tag_change_details(%Philomena.TagChanges.TagChange{__meta__: #Ecto.Schema.Metadata<:loaded, "tag_changes">, id: 12, user_id: nil, user: nil, image_id: 1, image: #Ecto.Association.NotLoaded<association :image is not loaded>, tags: [%Philomena.TagChanges.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tag_change_tags">, tag_change_id: 12, tag_change: #Ecto.Association.NotLoaded<association :tag_change is not loaded>, tag_id: 182, tag: %Philomena.Tags.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tags">, id: 182, aliased_tag_id: nil, aliased_tag: #Ecto.Association.NotLoaded<association :aliased_tag is not loaded>, aliases: #Ecto.Association.NotLoaded<association :aliases is not loaded>, channels: #Ecto.Association.NotLoaded<association :channels is not loaded>, implied_tags: #Ecto.Association.NotLoaded<association :implied_tags is not loaded>, implied_by_tags: #Ecto.Association.NotLoaded<association :implied_by_tags is not loaded>, verified_links: #Ecto.Association.NotLoaded<association :verified_links is not loaded>, public_links: #Ecto.Association.NotLoaded<association :public_links is not loaded>, hidden_links: #Ecto.Association.NotLoaded<association :hidden_links is not loaded>, dnp_entries: #Ecto.Association.NotLoaded<association :dnp_entries is not loaded>, slug: "meow", name: "meow", category: nil, images_count: 0, description: "", short_description: nil, namespace: nil, name_in_namespace: "meow", image: nil, image_format: nil, image_mime_type: nil, mod_notes: nil, uploaded_image: nil, removed_image: nil, implied_tag_list: nil, created_at: ~U[2025-06-16 18:57:37Z], updated_at: ~U[2025-06-16 18:57:37Z]}, added: true}], ip: %Postgrex.INET{address: {172, 18, 0, 7}, netmask: 32}, fingerprint: "d0547c7beb554b8", created_at: ~U[2025-06-16 19:12:04Z]})
    (philomena 1.2.1) lib/philomena_web/controllers/image/tag_change_controller.ex:45: PhilomenaWeb.Image.TagChangeController.delete/2
    (philomena 1.2.1) lib/philomena_web/controllers/image/tag_change_controller.ex:1: PhilomenaWeb.Image.TagChangeController.action/2
    (philomena 1.2.1) lib/philomena_web/controllers/image/tag_change_controller.ex:1: PhilomenaWeb.Image.TagChangeController.phoenix_controller_pipeline/2
    (phoenix 1.7.21) lib/phoenix/router.ex:484: Phoenix.Router.__call__/5
    (philomena 1.2.1) lib/philomena_web/endpoint.ex:1: PhilomenaWeb.Endpoint.plug_builder_call/2
    (philomena 1.2.1) deps/plug/lib/plug/debugger.ex:155: PhilomenaWeb.Endpoint."call (overridable 3)"/2
    (philomena 1.2.1) lib/philomena_web/endpoint.ex:1: PhilomenaWeb.Endpoint.call/2
    (phoenix 1.7.21) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
    (bandit 1.6.11) lib/bandit/pipeline.ex:130: Bandit.Pipeline.call_plug!/2
    (bandit 1.6.11) lib/bandit/pipeline.ex:40: Bandit.Pipeline.run/4
    (bandit 1.6.11) lib/bandit/http1/handler.ex:12: Bandit.HTTP1.Handler.handle_data/3
    (bandit 1.6.11) lib/bandit/delegating_handler.ex:18: Bandit.DelegatingHandler.handle_data/3
    (bandit 1.6.11) lib/bandit/delegating_handler.ex:8: Bandit.DelegatingHandler.handle_info/2
    (stdlib 6.2.2) gen_server.erl:2345: :gen_server.try_handle_info/3
    (stdlib 6.2.2) gen_server.erl:2433: :gen_server.handle_msg/6
    (stdlib 6.2.2) proc_lib.erl:329: :proc_lib.init_p_do_apply/3

Code:

lib/philomena_web/controllers/image/tag_change_controller.ex

52         body: &quot;Deleted tag change #{details} on image #{image.id} from history&quot;,
53         subject_path: ~p&quot;/images/#{image}/tag_changes&quot;
54       }
55     end
56   
57>    defp tag_change_details(%{user: %{name: name}, tags: tags}),
58       do: &quot;by #{name} containing #{Enum.count(tags)} change(s)&quot;
59   end

Called with 1 arguments

  • %Philomena.TagChanges.TagChange{__meta__: #Ecto.Schema.Metadata&lt;:loaded, &quot;tag_changes&quot;&gt;, id: 12, user_id: nil, user: nil, image_id: 1, image: #Ecto.Association.NotLoaded&lt;association :image is not loaded&gt;, tags: [%Philomena.TagChanges.Tag{__meta__: #Ecto.Schema.Metadata&lt;:loaded, &quot;tag_change_tags&quot;&gt;, tag_change_id: 12, tag_change: #Ecto.Association.NotLoaded&lt;association :tag_change is not loaded&gt;, tag_id: 182, tag: %Philomena.Tags.Tag{__meta__: #Ecto.Schema.Metadata&lt;:loaded, &quot;tags&quot;&gt;, id: 182, aliased_tag_id: nil, aliased_tag: #Ecto.Association.NotLoaded&lt;association :aliased_tag is not loaded&gt;, aliases: #Ecto.Association.NotLoaded&lt;association :aliases is not loaded&gt;, channels: #Ecto.Association.NotLoaded&lt;association :channels is not loaded&gt;, implied_tags: #Ecto.Association.NotLoaded&lt;association :implied_tags is not loaded&gt;, implied_by_tags: #Ecto.Association.NotLoaded&lt;association :implied_by_tags is not loaded&gt;, verified_links: #Ecto.Association.NotLoaded&lt;association :verified_links is not loaded&gt;, public_links: #Ecto.Association.NotLoaded&lt;association :public_links is not loaded&gt;, hidden_links: #Ecto.Association.NotLoaded&lt;association :hidden_links is not loaded&gt;, dnp_entries: #Ecto.Association.NotLoaded&lt;association :dnp_entries is not loaded&gt;, slug: &quot;meow&quot;, name: &quot;meow&quot;, category: nil, images_count: 0, description: &quot;&quot;, short_description: nil, namespace: nil, name_in_namespace: &quot;meow&quot;, image: nil, image_format: nil, image_mime_type: nil, mod_notes: nil, uploaded_image: nil, removed_image: nil, implied_tag_list: nil, created_at: ~U[2025-06-16 18:57:37Z], updated_at: ~U[2025-06-16 18:57:37Z]}, added: true}], ip: %Postgrex.INET{address: {172, 18, 0, 7}, netmask: 32}, fingerprint: &quot;d0547c7beb554b8&quot;, created_at: ~U[2025-06-16 19:12:04Z]}

Attempted function clauses (showing 1 out of 1)

 defp tag_change_details(%{user: %{name: name}, tags: tags})

lib/philomena_web/controllers/image/tag_change_controller.ex

40   
41       conn
42       |&gt; put_flash(:info, &quot;Successfully deleted tag change from history.&quot;)
43       |&gt; moderation_log(
44         details: &amp;log_details/2,
45>        data: %{image: image, details: tag_change_details(tag_change)}
46       )
47       |&gt; redirect(to: ~p&quot;/images/#{image}/tag_changes&quot;)
48     end
49   
50     defp log_details(_action, %{image: image, details: details}) do

lib/philomena_web/controllers/image/tag_change_controller.ex

1>  defmodule PhilomenaWeb.Image.TagChangeController do
2     use PhilomenaWeb, :controller
3   
4     alias Philomena.Images.Image
5     alias Philomena.TagChanges
6     alias Philomena.TagChanges.TagChange

lib/philomena_web/controllers/image/tag_change_controller.ex

1>  defmodule PhilomenaWeb.Image.TagChangeController do
2     use PhilomenaWeb, :controller
3   
4     alias Philomena.Images.Image
5     alias Philomena.TagChanges
6     alias Philomena.TagChanges.TagChange

lib/phoenix/router.ex

479           :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
480           halted_conn
481   
482         %Plug.Conn{} = piped_conn -&gt;
483           try do
484>            plug.call(piped_conn, plug.init(opts))
485           else
486             conn -&gt;
487               measurements = %{duration: System.monotonic_time() - start}
488               metadata = %{metadata | conn: conn}
489               :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)

lib/philomena_web/endpoint.ex

1>  defmodule PhilomenaWeb.Endpoint do
2     use Phoenix.Endpoint, otp_app: :philomena
3   
4     socket &quot;/socket&quot;, PhilomenaWeb.UserSocket,
5       websocket: true,
6       longpoll: false

deps/plug/lib/plug/debugger.ex

150             case conn do
151               %Plug.Conn{path_info: [&quot;__plug__&quot;, &quot;debugger&quot;, &quot;action&quot;], method: &quot;POST&quot;} -&gt;
152                 Plug.Debugger.run_action(conn)
153   
154               %Plug.Conn{} -&gt;
155>                super(conn, opts)
156             end
157           rescue
158             e in Plug.Conn.WrapperError -&gt;
159               %{conn: conn, kind: kind, reason: reason, stack: stack} = e
160               Plug.Debugger.__catch__(conn, kind, reason, stack, @plug_debugger)

lib/philomena_web/endpoint.ex

1>  defmodule PhilomenaWeb.Endpoint do
2     use Phoenix.Endpoint, otp_app: :philomena
3   
4     socket &quot;/socket&quot;, PhilomenaWeb.UserSocket,
5       websocket: true,
6       longpoll: false

lib/phoenix/endpoint/sync_code_reload_plug.ex

17   
18     def call(conn, {endpoint, opts}), do: do_call(conn, endpoint, opts, true)
19   
20     defp do_call(conn, endpoint, opts, retry?) do
21       try do
22>        endpoint.call(conn, opts)
23       rescue
24         exception in [UndefinedFunctionError] -&gt;
25           case exception do
26             %UndefinedFunctionError{module: ^endpoint} when retry? -&gt;
27               # Sync with the code reloader and retry once

lib/bandit/pipeline.ex

125       end
126     end
127   
128     @spec call_plug!(Plug.Conn.t(), plug_def()) :: Plug.Conn.t() | no_return()
129     defp call_plug!(%Plug.Conn{} = conn, {plug, plug_opts}) when is_atom(plug) do
130>      case plug.call(conn, plug_opts) do
131         %Plug.Conn{} = conn -&gt; conn
132         other -&gt; raise(&quot;Expected #{plug}.call/2 to return %Plug.Conn{} but got: #{inspect(other)}&quot;)
133       end
134     end
135   

lib/bandit/pipeline.ex

35         conn = build_conn!(transport, method, request_target, headers, opts)
36         span = Bandit.Telemetry.start_span(:request, measurements, Map.put(metadata, :conn, conn))
37   
38         try do
39           conn
40>          |&gt; call_plug!(plug)
41           |&gt; maybe_upgrade!()
42           |&gt; case do
43             {:no_upgrade, conn} -&gt;
44               %Plug.Conn{adapter: {_mod, adapter}} = conn = commit_response!(conn)
45               Bandit.Telemetry.stop_span(span, adapter.metrics, %{conn: conn})

lib/bandit/http1/handler.ex

7     @impl ThousandIsland.Handler
8     def handle_data(data, socket, state) do
9       transport = %Bandit.HTTP1.Socket{socket: socket, buffer: data, opts: state.opts}
10       connection_span = ThousandIsland.Socket.telemetry_span(socket)
11   
12>      case Bandit.Pipeline.run(transport, state.plug, connection_span, state.opts) do
13         {:ok, transport} -&gt; maybe_keepalive(transport, state)
14         {:error, _reason} -&gt; {:close, state}
15         {:upgrade, _transport, :websocket, opts} -&gt; do_websocket_upgrade(opts, state)
16       end
17     end

lib/bandit/delegating_handler.ex

13       |&gt; handle_bandit_continuation(socket)
14     end
15   
16     @impl ThousandIsland.Handler
17     def handle_data(data, socket, %{handler_module: handler_module} = state) do
18>      handler_module.handle_data(data, socket, state)
19       |&gt; handle_bandit_continuation(socket)
20     end
21   
22     @impl ThousandIsland.Handler
23     def handle_shutdown(socket, %{handler_module: handler_module} = state) do

lib/bandit/delegating_handler.ex

3     # Delegates all implementation of the ThousandIsland.Handler behaviour
4     # to an implementation specified in state. Allows for clean separation
5     # between protocol implementations &amp; friction free protocol selection &amp;
6     # upgrades.
7   
8>    use ThousandIsland.Handler
9   
10     @impl ThousandIsland.Handler
11     def handle_connection(socket, %{handler_module: handler_module} = state) do
12       handler_module.handle_connection(socket, state)
13       |&gt; handle_bandit_continuation(socket)

gen_server.erl

No code available.

gen_server.erl

No code available.

proc_lib.erl

No code available.

Connection details

Params

%{"_csrf_token" => "MwlwGDZKMjQAJwwDOxslI04-bAsEO3JaizIsOzxDxhZ7wTrH6XXfIUJh", "_method" => "delete", "id" => "12", "image_id" => "1"}

Request info

Headers

  • accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
  • accept-encoding: gzip, deflate
  • accept-language: en-US,en;q=0.5
  • content-length: 83
  • content-type: application/x-www-form-urlencoded
  • cookie: _philomena_key=XCP.QPwcSLlLSZmI5U_DGzB563rbkB_eSvek3Dl5OCGLg5MGYWWUT-3lkV18_aMForYW0yyr-kRuc36s-rMffdmzcg1sPzs6g00p4X8wo4txgY6MmYGCczBsLv6Q7jxCcuescIJyspWk13O3NfNUEVv0Psh4blNp9A_jn_aJuGnfPbwb0GLe_S3I-EyhZBCQB3IuBd0hb1K4uZ7G8wDoj3ZPFaCEOj0KrBlRQvLvmywcr7ma1Lb5TP9O5l4t0C7Fu6j-56FE-Xn8cjEpCdOAmmOt8zEqQz4F2k0E-fxbUcwJGAlrOsaW76xm9yRG8QGAEHYwQjDK0PwJ14M6PEsQ__PKoe1h_w37RIqmsV7qa6EgSCB1goYOinOAk5Axc; _ses=d0547c7beb554b8
  • dnt: 1
  • host: 172.19.129.35:8080
  • origin: http://172.19.129.35:8080
  • priority: u=0, i
  • referer: http://172.19.129.35:8080/images/1/tag_changes
  • sec-gpc: 1
  • upgrade-insecure-requests: 1
  • user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0
  • via: 1.1 Caddy
  • x-forwarded-for: 172.18.0.1
  • x-forwarded-host: 172.19.129.35:8080
  • x-forwarded-proto: http

Session

%{"_csrf_token" => "Zs9ky0JpxOV4LOWkxf4mMn82", "_ses" => "d0547c7beb554b8", "live_socket_id" => "users_sessions:L9qZTsionUR9HGFgs_JMz1Nj4_RkqQtyD46mG0sv7bM=", "remote_ip" => "172.18.0.7", "user_token" => <<47, 218, 153, 78, 200, 168, 157, 68, 125, 28, 97, 96, 179, 242, 76, 207, 83, 99, 227, 244, 100, 169, 11, 114, 15, 142, 166, 27, 75, 47, 237, 179>>}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions