Workflow yang gagal bukan akhir dunia bila tahu cara membaca jejaknya. Di episode ini kita membuka debug logging, masuk langsung ke runner lewat SSH interaktif dengan tmate, lalu membangun custom action berbasis TypeScript maupun container Docker.

Di episode 18 kita mengotomasi release management dan semantic versioning. Saat pipeline makin kompleks, satu hal pasti terjadi: workflow gagal pada jam 2 pagi. Kemampuan men-debug dan memperbaiki pipeline sama pentingnya dengan kemampuan membangunnya. Di episode ini kita belajar membaca jejak kegagalan, membuka debug logging, masuk ke runner secara interaktif, lalu memproduksi custom action sendiri supaya logika yang berulang bisa dikemas dan dipakai ulang.
Di episode ini kita membahas:
Pendekatan berjenjang, mulai dari yang termurah:
Dua repository secret memicu log yang jauh lebih detail:
| Secret | Efek |
|---|---|
| ACTIONS_RUNNER_DEBUG | Log diagnosa runner: pemilihan job, download action, eksekusi step |
| ACTIONS_STEP_DEBUG | Log tambahan per step, termasuk ekspor variabel dan konteks action |
Set kedua secret bernilai true di repository Settings → Secrets and variables → Actions. Log tambahan muncul sebagai baris ##[debug] pada setiap step. Setelah selesai men-debug, hapus secret atau ubah nilainya kembali ke false — log debug membuat log membengkak dan memperlambat pipeline.
Ketika log tidak cukup — misalnya error hanya muncul saat interaksi tertentu — kita bisa menyambungkan diri langsung ke runner yang sedang hidup lewat SSH menggunakan mxschmitt/action-tmate@v3. Tambahkan step sementara ke workflow:
name: Debug Runner
on: workflow_dispatch
jobs:
debug:
runs-on: ubuntu-latest
steps:
- name: Checkout kode
uses: actions/checkout@v4
- name: Buka sesi SSH interaktif
uses: mxschmitt/action-tmate@v3
with:
timeout-minutes: 30Saat step ini berjalan, log menampilkan alamat SSH tmate seperti ssh alice@proxy.tmate.io. Dari laptop, kalian bisa masuk lalu menjalankan ls, ps aux, atau perintah lain langsung di runner untuk mengamati kondisi nyata. Layar mencetak status session sehingga kalian tahu kapan runner siap.
Warning
tmate membuka akses shell penuh ke runner bagi siapa pun yang memegang alamat SSH. Hanya aktifkan pada workflow sementara, jangan pernah dibiarkan di branch main, dan jangan gunakan di repository public dengan self-hosted runner.
Kalau sudah beres, hapus step tmate dari workflow — kalau tidak, setiap run akan berhenti menunggu koneksi sampai timeout.
Tanda-tanda logika perlu dikemas menjadi action:
Ada tiga jenis custom action: JavaScript/TypeScript, Docker container, dan composite. Di episode ini kita fokus ke dua yang pertama; composite sudah dibahas di episode 11.
Action TypeScript dikompilasi menjadi satu file dist/index.js yang dijalankan langsung oleh runner dengan runtime Node.js. Karena tidak perlu build image, eksekusinya cepat — cocok untuk logika pemrosesan.
Setiap action wajib punya action.yml di root repository action:
name: 'Compute Next Version'
description: 'Menghitung versi berikutnya dari commit message Conventional Commits'
author: 'Arman'
inputs:
release-type:
description: 'Jenis rilis: patch, minor, atau major'
required: true
prefix:
description: 'Prefix versi, misal v'
required: false
default: 'v'
outputs:
version:
description: 'Versi yang dihitung'
runs:
using: node20
main: dist/index.js
branding:
icon: 'tag'
color: 'purple'Kode sumber ditulis TypeScript, memakai @actions/core untuk input/output dan @actions/github untuk akses konteks GitHub:
import * as core from '@actions/core'
import * as github from '@actions/github'
async function run(): Promise<void> {
try {
const releaseType = core.getInput('release-type')
const prefix = core.getInput('prefix')
const ref = github.context.ref
const version = computeVersion(releaseType, ref)
core.setOutput('version', `${prefix}${version}`)
} catch (error) {
core.setFailed(error instanceof Error ? error.message : 'Terjadi error')
}
}
function computeVersion(releaseType: string, ref: string): string {
return `1.${releaseType === 'minor' ? 1 : 0}.0`
}
run()Penjelasan:
core.getInput() membaca input yang dideklarasikan di action.yml; core.setOutput() menulis output yang bisa dibaca workflow pemanggil.github.context memberi akses ke konteks seperti repository, ref, dan actor.core.setFailed() membuat action selesai dengan status gagal dan pesan yang jelas.Runner Node.js menjalankan dist/index.js, jadi kode sumber harus dikompilasi sebelum di-commit. Bangun dengan esbuild, lalu commit hasilnya:
npm install --save-dev esbuild typescript @types/node
npx esbuild src/main.ts --bundle --platform=node --target=node20 \
--outfile=dist/index.js
git add dist/index.js
git commit -m "chore: build dist"Karena action dipakai lewat tag atau SHA commit, dist/index.js harus selalu ter-update dan ter-commit — kalau lupa, action akan berjalan dengan kode lama. Bundling dengan esbuild merangkum @actions/core dan @actions/github ke dalam satu file, jadi konsumen action tidak perlu npm install apa pun.
Tip
Pasang eslint untuk memeriksa kode action, dan pastikan file dist di-build pada setiap perubahan source. Banyak tim membungkus kompilasi ke dalam workflow CI action itu sendiri agar tidak pernah lupa.
Action Docker dijalankan di dalam container — bebas memilih bahasa dan dependensi, tanpa khawatir versi runtime di runner. Cocok untuk tool yang butuh image sendiri (misalnya gcloud atau kubectl versi tertentu). Kelemahannya: eksekusi lebih lambat karena harus build atau pull image dulu.
name: 'Run Cloud Migrations'
description: 'Menjalankan skema migrasi database di cloud'
inputs:
environment:
description: 'Target environment'
required: true
runs:
using: docker
image: Dockerfile
env:
ENVIRONMENT: ${{ inputs.environment }}Image dibangun dari Dockerfile, lalu container menjalankan entrypoint:
FROM alpine:3.20
RUN apk add --no-cache python3 py3-pip
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]#!/bin/sh
set -euo pipefail
echo "Menjalankan migrasi untuk environment: ${INPUT_ENVIRONMENT}"
python3 /app/migrate.py --env "${INPUT_ENVIRONMENT}"Penjelasan:
INPUT_ dalam huruf besar: input environment menjadi INPUT_ENVIRONMENT.set -euo pipefail menghentikan container saat ada error, yang membuat action gagal dengan benar.chmod +x), dan pastikan tidak ada CRLF pada file — set core.autocrlf di git untuk menghindari error not found misterius.Di episode ini kita membahas troubleshooting dan custom action development:
##[debug] berkat secret ACTIONS_RUNNER_DEBUG dan ACTIONS_STEP_DEBUG.dist/index.js dengan @actions/core dan @actions/github.INPUT_.Semua puzzle otomasi sudah lengkap: trigger, job, artifact, secret, deploy, rilis, dan kini custom action. Di episode 20, episode pamungkas, kita merangkai semuanya menjadi studi kasus pipeline CI/CD produksi lengkap end-to-end. Sampai jumpa!