Jeśli kiedykolwiek próbowałeś wrzucić raport QA do Jira Cloud tak, żeby obrazki renderowały się inline, to znasz to uczucie. Wgrywasz screenshota. Wskazujesz go w treści. A Jira pokazuje Ci szary prostokąt z napisem “External media” i linkiem. Nie obrazek.

Siedziałem nad tym godzinami. Nie minutami. Godzinami.

I teraz rzecz, której nikt nie mówi: to nie jest Twój błąd i to nie jest bug. Tak działa Atlassian Document Format. A kiedy raz zobaczysz mechanizm, cała sprawa robi się deterministyczna. Zero zgadywania. Zero LLM. Same bajty robiące to, co bajty robią.

To część 1 z dwóch. Dziś: problem i trik, który go rozwiązuje. W części 2: pełny pipeline i repo, które sklonujesz.

Problem, dokładnie

Jira Cloud trzyma treść jako ADF - Atlassian Document Format. To JSON. Komentarz to drzewo węzłów. Akapity, tekst, tabele, media.

Żeby pokazać obrazek inline, ADF potrzebuje węzła media. Ten węzeł nie wskazuje na Twój załącznik. Wskazuje na media UUID - wewnętrzny identyfikator obrazka w magazynie mediów Atlassiana.

I tu jest pułapka. Kiedy wgrywasz załącznik przez REST API, dostajesz attachment id. Nie dostajesz media UUID. To dwie różne rzeczy. Samo attachment id nie wyrenderuje obrazka inline. Większość ludzi odkrywa to na własnej skórze, odpuszcza i wkleja link zamiast obrazka.

Więc prawdziwe pytanie brzmi: jak zamienić attachment id na media UUID?

Trik z 303

Jest taki endpoint:

GET /rest/api/3/attachment/content/{attachmentId}

Wołasz go, a Jira nie podaje Ci pliku. Odpowiada 303 See Other z nagłówkiem Location. I ten URL w Location zawiera media UUID.

To cały sekret. UUID siedzi w przekierowaniu, którego nigdy nie miałeś przeczytać.

Tyle że większość klientów HTTP podąża za przekierowaniami automatycznie. Twój fetch podąża, pobiera binarkę i wyrzuca nagłówek Location, zanim go w ogóle zobaczysz. Masz bajty pliku. Nie masz UUID. I nie masz pojęcia dlaczego.

Naprawa to jedna flaga:

fetch(url, { redirect: 'manual' })

Ręczna obsługa przekierowania. Nie podążaj. Sam przeczytaj nagłówek Location. Wyciągnij UUID regexem. Gotowe.

Przypis o n8n, który kosztował mnie popołudnie

Jeśli budujesz to w n8n, tak jak ja, jest druga pułapka na wierzchu pierwszej.

Nowszy helper this.helpers.httpRequest podąża za przekierowaniami i nie daje czystego sposobu, żeby go zatrzymać w sandboxie 2.x. Więc połyka nagłówek Location, dokładnie jak naiwny fetch. I siedzisz, gapiąc się na odpowiedź 200, zastanawiając się, gdzie zniknęło Twoje przekierowanie.

Starszy this.helpers.request pozwala podać followRedirect: false. To ten, którego chcesz. Popołudnie mojego życia, dokładnie tam, w różnicy między dwiema metodami helpera.

Mówię Ci o tym, żeby Cię to kosztowało pięć minut zamiast popołudnia.

Dlaczego to ważne poza jednym obrazkiem

To jest teza wszystkiego, co buduję: kontekst przed LLM.

Rozwiązanie media UUID to nie robota dla modelu. To przekierowanie, nagłówek i regex. Jest w 100% deterministyczne. Ten sam input, ten sam output, za każdym razem. Jeśli rzucisz na to LLM, płacisz tokenami za coś, co parser bajtów robi za darmo, i robisz to mniej niezawodnie.

Obrazek należy do deterministycznej podłogi. Model - jeśli w ogóle go używasz - decyduje, co napisać. Transport decyduje, jak to ląduje w Jirze. To różne warstwy, a ich mieszanie to przepis na wolne, drogie i niestabilne raporty.

W części 2 pokażę pełny flow - create, upload, resolve, embed - wzorzec buildera multi-stage za nim, i repo, które sklonujesz i odpalisz na mockowej Jirze w niecałą minutę.

Kod jest publiczny. Link w części 2 i w pierwszym komentarzu.