Blog

  • Enable Vertical Tabs on Chrome Browser

    Open Chrome Browser -> Enter chrome://flags

    Enable Vertical Tabs in Experiments

    View -> Click Show Tabs Vertically

    That’s it. You can see Vertical Tabs in Chrome Browser.

  • MacOS Native App Review series #1 – Git GUI Tool – Fork

    MacOS Native App Review series #1 – Git GUI Tool – Fork

    I’ve used fork app for more than 5 years now.

    This app is light, built by native. It’s fast and easy to manage git branches and commits.

    https://git-fork.com

    It also supports the command line tools. I only use fork in terminal to open the current folder in fork app. It’s quite useful when you are working on terminal with claude code.

  • Tip for preventing crash issues when you migrate CoreData

    CoreData supports 3 type of migration

    • LightWeight Migrations
    • Staged Migrations
    • Manual Migrations
    screenshot 2026 01 01 at 8.20.42 pm

    Case 1. Manual Migration

    When you faced this issue, You need to check next version of the xcdatamodel.

    Case 2. LightWeight Migration

    Most common crash issues are caused by mismatching options in a properties. For example you have a Binary Data type field with external storage. And when your next xcdatamodel doesn’t matching with external storage options, it causes crash issues

    Lastly I recommend adding Arguments to investigate issues

    • -com.apple.CoreData.SQLDebug
    • -com.apple.CoreData.ConcurrencyDebug
    • -com.apple.CoreData.MigrationDebug
    screenshot 2026 01 01 at 8.49.46 pm

  • Swift Vapor: How to run a scheduled job

    Step 1. Install Redis on your Mac

    Follow the official guide

    https://redis.io/docs/latest/operate/oss_and_stack/install/archive/install-redis/install-redis-on-mac-os/

    brew install redis
    
    brew services start redis
    
    brew services info redis
    
    redis-cli
    screenshot 2025 12 28 at 11.38.31 am

    Step 2. Setup Redis Configuration in Vapor App

    Add Redis Swift Package

    .package(url: "https://github.com/vapor/queues-redis-driver.git", from: "1.0.0")

    // swift-tools-version:6.0
    import PackageDescription
    
    let package = Package(
        name: "TestServer",
        platforms: [
            .macOS(.v13)
        ],
        dependencies: [
            // 💧 A server-side Swift web framework.
            .package(url: "https://github.com/vapor/vapor.git", from: "4.111.0"),
            .package(url: "https://github.com/vapor/fluent", from: "4.12.0"),
            .package(url: "https://github.com/vapor/fluent-sqlite-driver", from: "4.8.0"),
            .package(url: "https://github.com/vapor/sql-kit", from: "3.33.2"),
            .package(url: "https://github.com/lukaskubanek/LoremSwiftum", from: "2.2.3"),
            .package(url: "https://github.com/vapor/fluent-postgres-driver", from:"2.10.0"),
            .package(url: "https://github.com/vapor/jwt", from: "5.1.2"),
            .package(url: "https://github.com/vapor/queues-redis-driver.git", from: "1.0.0"),
        ],
        targets: [
            .target(
                name: "App",
                dependencies: [
                    .product(name: "Vapor", package: "vapor"),
                    .product(name: "Fluent", package: "fluent"),
                    .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
                    .product(name: "SQLKit", package: "sql-kit"),
                    .product(name: "LoremSwiftum", package: "LoremSwiftum"),
                    .product(name: "JWT", package: "jwt"),
                    .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
                    .product(name: "QueuesRedisDriver", package: "queues-redis-driver")
                ],
                swiftSettings: [
                    // Enable better optimizations when building in Release configuration. Despite the use of
                    // the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release
                    // builds. See <https://github.com/swift-server/guides#building-for-production> for details.
                    .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
                ]
            ),
            .executableTarget(name: "Run", dependencies: [
                    .target(name: "App")
                ]
            ),
            .testTarget(name: "AppTests", dependencies: [
                .target(name: "App"),
                .product(name: "XCTVapor", package: "vapor")
            ])
        ]
    )
    

    Create an AsyncScheduledJob

    import Foundation
    import Vapor
    import Queues
    
    struct ScheduledJobs: AsyncScheduledJob {
        // Add extra services here via dependency injection, if you need them.
        func run(context: QueueContext) async throws {
            context.logger.info("Starting ScheduledJobs")
            print("✅ It is called")
            //Call other services using context.application.client
    //        context.application.client
            context.logger.info("ScheduledJobs completed")
        }
    }
    

    Register Scheduled Job in Vapor App

    https://docs.vapor.codes/advanced/queues/#available-builder-methods

    import Vapor
    import QueuesRedisDriver
    
    public func configure(_ app: Application) throws {
        let redisConfig = try RedisConfiguration(
            hostname: "127.0.0.1",
            port: 6379,
            pool: .init(
                maximumConnectionCount: .maximumActiveConnections(50),
                minimumConnectionCount: 10
            )
        )
        app.redis.configuration = redisConfig
        //Use Redis
        app.queues.use(.redis(redisConfig))
        
        //Register ScheduledJob - It runs every 30 seconds
        app.queues.schedule(ScheduledJobs())
            .minutely()
            .at(30)
        
        try app.queues.startScheduledJobs()
    
    ....
    }
    screenshot 2025 12 28 at 11.51.30 am

    Conclusion

    Hope my articles helps you who want to run scheduled job using Redis. Please like my post or leave a comment it helps me continue share my knowledge for free.

  • 태국 치앙마이 체류 연장신청 후기 90 -> 97일

    태국 치앙마이 체류 연장신청 후기 90 -> 97일

    한국 여권은 태국에 90일까지 머물수 있다.

    작년에 치앙마이에서 정확하게 97일 연속으로 체류했었다.

    참고로 가장 쉬운 방법은 태국을 떠나 다른 나라로 갔다가 다시 태국으로 돌아오는 방법이다. 하지만 나는 그럴 여유가 없었고 영어학원 종료일과 콘도 계약기간이 7일이나 남아있어서 태국에 체류하면서 연장을 해야 했다.

    이 포스트는 태국에 머무르면서 1주일 연장 하는 방법에 대한 포스팅이다. (이 포스팅은 2024년 10월 기준 정보이다)

    태국은 반드시 90일 이상 체류 시 신고를 해야 된다.

    태국 체류 1주일 연장 신청 방법

    img 3060

    나는 디콘도에 살았었다. 가장 가까운 센트럴 치앙마이 몰에 이미그레이션 센터가 있다.

    img 2004

    쇼핑몰 안에 있는 이미그레이션 센터.

    img 2005

    방문 전에 알아야 할 것들과 준비해야 될 서류들

    img 2738

    먼저 TM30이라는 서류 양식이 있다. 정확하게 어떤 내용을 기입했는지 기억나지 않지만 집 계약문서 혹은 집주인으로부터 발급받아야 되는 걸로 기억한다.

    img 2740 1

    위의 서류는 거주자 신고서 양식이다. 이건 온라인으로 제출했었다.

    서류를 미리 작성하고 방문을 하면 이미그레이션 센터 직원에서 서류 검토를 해준다. 검토 후 이상이 없으면 다음과 같은 번호표를 발급해준다.

    img 2757

    비자체류 승인 완료

    screenshot 2025 11 09 at 11.39.16 pm

    비자 승인이 완료되면 여권에 위와 같은 도장을 찍어준다.

    원래 입국 시 찍어준 도장은 10월 22일까지 체류할 수 있었는데 체류 연장 이후 10월 29일까지 머무를 수 있도록 도장 찍어줬다.

  • 싱가포르 조호바루 기차표 예약

    싱가포르 조호바루 기차표 예약

    EasyBook.com에서 예약했다.

    screenshot 2025 11 09 at 10.02.59 pm

    https://www.easybook.com

    사이트에 가입해서 구매했다. 이전에는 싱가포리언 친구 차를 타고 편하게 갔었는데 이번에 처음으로 기차타고 가보기로 결심.

    싱가포르 우드랜드 기차역에서 JB Sentral까지는 금액이 편도로 인당 6.8불이다.

    screenshot 2025 11 09 at 10.35.18 pm

    반대로 JB Sentral에서 우드랜드로 돌아오는 표는 오히려 더 저렴하다. 인당 2.14불.

    결제는 PayNow로 완료함.

    티켓 영수증 확인하기

    screenshot 2025 11 09 at 10.36.57 pm

    생각보다 어렵지 않은 티케팅. 금액도 아주 저렴하고 만족스럽다. 다음 포스팅에서는 조호바루 여행 후기를 남길 예정.

  • Server-Side Swift – AWS Lambda with OpenAPI Generator Part 1

    screenshot 2025 10 26 at 10.37.10 pm

    This post is for whom want to use AWS Lambda with OpenAPI Generator. Official guide is useful but I felt there are some missing information. So I wrote this post. You can successfully run AWS Lambda function on your local machine and debug your code.

    Let’s start from very simple example. You only need 4 files. I’ll explain details. (Ignore Tests folder, no need it in this tutorial)

    • Package.swift
    • NativeMobileServer.swift
    • openapi.yaml
    • openapi-generator-config.yaml
    screenshot 2025 10 26 at 10.49.32 pm

    Step 1. Define OpenAPI Spec

    This OpenAPI spec is for tutorial.

    Please check folder and file structure. Create an openapi.yaml

    openapi: 3.1.0
    info:
      title: MobileJobService
      version: 1.0.0
    
    paths:
      /jobs/fetch:
        post:
          summary: Fetch job data from external source
          description: >
            This endpoint is called by a scheduled Lambda or backend service.
            It fetches job data from the given URL and processes it using the provided prompt.
          operationId: fetchJobs
          tags:
            - jobs
          requestBody:
            required: true
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/FetchJobsRequest'
          responses:
            '200':
              description: Successfully fetched and processed job data
              content:
                application/json:
                  schema:
                    $ref: '#/components/schemas/JobListResponse'
            '400':
              description: Invalid input parameters
            '500':
              description: Internal error during job fetch or processing
    
    components:
      schemas:
        FetchJobsRequest:
          type: object
          required:
            - url
            - prompt
          properties:
            url:
              type: string
              format: uri
              description: Target URL to scrape or fetch job data from
            prompt:
              type: string
              description: Instruction / extraction prompt used to parse the fetched page
    
        JobListResponse:
          type: array
          items:
            $ref: '#/components/schemas/Job'
    
        Job:
          type: object
          required:
            - id
            - title
          properties:
            id:
              type: string
              description: Unique identifier for the job
            title:
              type: string
              description: Job title
            country:
              type: string
              description: Country code (e.g. SG, US, TW)
            city:
              type: string
              description: City name (e.g. Singapore)
            postedAt:
              type: string
              format: date-time
              description: When this job was posted, if known
            company:
              type: string
              description: Company name
            team:
              type: string
              description: Team / department (e.g. Mobile, Backend, Growth)
            jobDescriptionLink:
              type: string
              format: uri
              description: Public link to full job description
            jobApplyLink:
              type: string
              format: uri
              description: Public link to apply
            salary:
              $ref: '#/components/schemas/Salary'
            description:
              type: string
              description: Cleaned / extracted full-text description for the role
    
        Salary:
          type: object
          properties:
            min:
              type: number
              description: Minimum compensation
            max:
              type: number
              description: Maximum compensation
            basis:
              type: string
              enum: [year, month]
              description: Salary period basis (yearly or monthly)
    

    And then create openapi-generator-config.yaml

    generate:
      - types
      - server
    

    Step 2. Create Swift Package Manger

    Package.swift

    // swift-tools-version: 6.2
    // The swift-tools-version declares the minimum version of Swift required to build this package.
    
    import PackageDescription
    
    let package = Package(
        name: "NativeMobileServer",
        platforms: [
            .macOS(.v15)
        ],
        products: [
            .executable(name: "NativeMobileServer", targets: ["NativeMobileServer"])
        ],
        dependencies: [
            .package(url: "https://github.com/apple/swift-openapi-generator.git", from: "1.10.3"),
            .package(url: "https://github.com/apple/swift-openapi-runtime.git", from: "1.8.2"),
    
            .package(url: "https://github.com/awslabs/swift-aws-lambda-runtime.git", from: "2.0.0"),
            .package(url: "https://github.com/awslabs/swift-aws-lambda-events.git", from: "1.2.0"),
            .package(url: "https://github.com/awslabs/swift-openapi-lambda.git", from: "2.0.0"),
    
            .package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.0.0"),
        ],
        targets: [
            .executableTarget(
                name: "NativeMobileServer",
                dependencies: [
                    .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
    
                    .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
                    .product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
    
                    .product(name: "OpenAPILambda", package: "swift-openapi-lambda"),
                    .product(name: "ServiceLifecycle", package: "swift-service-lifecycle"),
                ],
                path: "Sources/NativeMobileServer",
                resources: [
                    .copy("openapi.yaml"),
                    .copy("openapi-generator-config.yaml")
                ],
                plugins: [
                    .plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")
                ]
            ),
            .testTarget(
                name: "NativeMobileServerTests",
                dependencies: ["NativeMobileServer"]
            ),
        ]
    )
    

    You can remove .testTarget if you don’t need to write test cases.

    Step 3. Write a main function

    //
    //  NativeMobileServer.swift
    //  NativeMobileServer
    //
    //  Created by Shawn Sungwook Baek on 10/26/25.
    //
    
    import Foundation
    import Logging
    import OpenAPILambda
    import OpenAPIRuntime
    
    @main
    struct JobServiceImpl: APIProtocol, OpenAPILambdaHttpApi {
        func fetchJobs(_ input: Operations.fetchJobs.Input) async throws -> Operations.fetchJobs.Output
        {
            logger.info("fetchJobs invoked")
            let requestBody = input
           
            if case let .json(request) = requestBody.body {
                logger.info("Fetching jobs from \(request.url) with prompt: \(request.prompt)")
            }
    
            let mockJobs: [Components.Schemas.Job] = [
                .init(
                    id: "job-001",
                    title: "iOS Developer",
                    country: "SG",
                    postedAt: Date(),
                    company: "Apple",
                    team: "Mobile",
                    jobDescriptionLink: "https://example.com/job/1",
                    jobApplyLink: "https://example.com/apply/1",
                    salary: .init(min: 6000, max: 9000, basis: .month),
                    description: "Develop and maintain the iOS application."
                ),
                .init(
                    id: "job-002",
                    title: "Backend Engineer",
                    country: "TW",
                    postedAt: Date(),
                    company: "Uber",
                    team: "Server",
                    jobDescriptionLink: "https://example.com/job/2",
                    jobApplyLink: "https://example.com/apply/2",
                    salary: .init(min: 7000, max: 10000, basis: .month),
                    description: "Build API services and integrations."
                ),
            ]
            return .ok(.init(body: .json(mockJobs)))
        }
    
        let logger: Logger
    
        func register(transport: OpenAPILambdaTransport) throws {
            try transport.router.get("/health") { _, _ in
                "OK"
            }
            logger.trace("Available Routes\n\(transport.router)")
            // to log all requests and their responses, add a logging middleware
            let loggingMiddleware = LoggingMiddleware(logger: logger)
    
            // MANDATORY (middlewares are optional)
            try self.registerHandlers(on: transport, middlewares: [loggingMiddleware])
        }
    
        static func main() async throws {
            let openAPIService = JobServiceImpl(loggerLabel: "JobService")
            try await openAPIService.run()
    
        }
    
        init(loggerLabel: String) {
            var logger = Logger(label: loggerLabel)
            logger.logLevel = .trace
            self.logger = logger
        }
    }
    

    Step 4. Check Scheme in Xcode

    screenshot 2025 10 26 at 10.52.56 pm

    Check Executable Target.

    screenshot 2025 10 26 at 10.54.39 pm

    Important!

    You need to set Environment Variables

    • LOCAL_LAMBDA_SERVER_ENABLED
    • LOCAL_LAMBDA_PORT <- Optional! Default port is 7000

    Try build and run and check which pid is using 7000 port.

    lsof -i :7000

    If other program uses that port, you can change default port by setting LOCAL_LAMBDA_PORT

    screenshot 2025 10 26 at 10.57.10 pm

    When you run it, you will see the message. (In my case, I changed the default port 8000)

    Step 5. Invoke Lambda function

    This part you may confusing because you can’t call lambda like

    curl -v -X POST http://127.0.0.1:8000/jobs/fetch \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://example.com/jobs",
        "prompt": "Extract latest mobile job listings"
      }'
    screenshot 2025 10 26 at 11.01.22 pm

    If you want to see more detail information, check this out

    To invoke and debug our code, we should invoke function like this.

    If you want to learn more about this payload structure v2.0 in API Gateway check here

    curl -X "POST" "http://127.0.0.1:8000/invoke" \
         -H 'Content-Type: application/json; charset=utf-8' \
         -d $'{
      "requestContext": {
        "accountId": "",
        "time": "",
        "http": {
          "path": "/jobs/fetch",
          "userAgent": "",
          "method": "POST",
          "protocol": "HTTP/1.1",
          "sourceIp": "127.0.0.1"
        },
        "domainName": "",
        "timeEpoch": 0,
        "domainPrefix": "",
        "apiId": "",
        "requestId": "",
        "stage": "$default"
      },
      "rawPath": "/jobs/fetch",
      "rawQueryString": "",
      "version": "2.0",
      "routeKey": "$default",
      "isBase64Encoded": true,
      "body": "{\\"url\\":\\"https://jobs.apple.com/en-sg/search\\", \\"prompt\\":\\"Extract latest mobile job listings\\"}"
    }'

    I’ve set breakpoint at here.

    screenshot 2025 10 26 at 11.07.43 pm

    Let’s invoke function.

    screenshot 2025 10 26 at 11.09.43 pm

    Ok, breakpoint is working.

    screenshot 2025 10 26 at 11.09.04 pm

    And I can see the results from AWS Lambda function

    Conclusion

    Swift is very powerful for developing server-side applications. There a lot of great open source projects. In Part 2, I’ll explain how to deploy AWS Lambda Swift function to the AWS using SAM CLI.

  • Apply Swift Format

    Apply Swift Format

    Swift Format is made by Apple. If your Xcode Version is latest version (after Xcode 16), you don’t need to install it. Toolchain contains swift format.

    xcrun --find swift-format
    
    ///Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-format

    https://github.com/swiftlang/swift-format?tab=readme-ov-file#configuring-the-command-line-tool

    Step 1. Create swift-format.json

    swift-format dump-configuration > swift-format.json
    
    //It will show default settings for formatting and listing

    Copied default settings and create a file like swift-format.json in your project root directory.

    SampleProject/
    ├── 📱 SampleProject.xcworkspace
    ├── 🔧 SampleProject.xcodeproj
    │
    ├── swift-format.json               # Swift Format
    ├── 📦 Main App & Server
    │   ├── SampleProject/              # iOS 
    │   ├── SampleProjectTests/
    │   └── SampleProjectServer/        # Server
    │
    ├── 🧩 Modules (Core Libraries)
    │
    ├── 📦 Dependencies
    │   └── Packages/                  # SPM packages
    │
    └── 📄 Config Files
        ├── SampleProject.xcconfig
        └── GoogleService-Info.plist

    Step 2. Add Build Script

    screenshot 2025 10 04 at 11.07.55 pm
    echo "🐥 Formatting"
    swift-format format --configuration swift-format.json --ignore-unparsable-files -i -p -r ${PROJECT_DIR}
        
    echo "🐥 Linting"
    swift-format lint --configuration swift-format.json --ignore-unparsable-files -p -r ${PROJECT_DIR}

    Step 3. Build Project

    screenshot 2025 10 04 at 11.24.04 pm

    Okay It works

  • Setup C/C++ on the iPad using iSH Shell

    Setup C/C++ on the iPad using iSH Shell

    Do you want to use a terminal on your iPad?

    I recommend iSH Shell.

    You can install packages on your iPad and it works on the offline.

    img 0581

    Step 1. Download iSH shell

    https://apps.apple.com/kr/app/ish-shell/id1436902243?l=en-GB

    Step 2. Install APK packages

    apk update
    
    apk add gcc g++ make musl-dev
    
    apk add git
    
    apk add vim

    Step 3. Install zsh

    apk add zsh curl git
    
    sh -c “$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)”
    img 0577

    And set zsh as default

    ask add shadow
    
    passwd
    
    chsh -s /bin/zsh

    To run chsh, you have to set your password. But on the iSH shell, you haven’t set any password. So please set a new password by running passwd.

    And then run chsh -s /bin/zsh

    Step 4. Write C++ Code

    vim hello.cpp
    #include <iostream>;
    using namespace std;
    
    int main() {
        cout &lt;&lt; “Hellow from iPad” &lt;&lt; endl;
        return 0;
    }
    g++ hello.cpp -o hello
    
    ./hello
    img 0580

    Isn’t it cool? 😎 Happy c++ coding on your iPad on the offline!

  • How to Validate an OpenAPI Spec Using IBM OpenAPI Validator

    How to Validate an OpenAPI Spec Using IBM OpenAPI Validator

    This post is the second part of our OpenAPI series. Let’s set up GitHub Actions for validating an OpenAPI spec before merging into the main branch.

    Check more details

    Step 1. Install IBM OpenAPI Validator

    npm i -g ibm-openapi-validator
    npm i @ibm-cloud/openapi-ruleset

    Step 2. Setup OpenAPI Rullset

    extends: '@ibm-cloud/openapi-ruleset'
    rules:
      ibm-accept-and-return-models: info
      ibm-integer-attributes: false
      ibm-required-array-properties-in-response: false
      ibm-property-casing-convention: false
    

    Create an file – validator-rules.yaml

    You can check details

    ibm rules (inherent from spectral:as rules)

    spectral:oas rules

    Step 3. Setup Configurations

    Create an file – validator-config.yml

    errorsOnly: true
    colorizeOutput: true
    limits:
      warnings: 25
    outputFormat: 'text'
    summaryOnly: false
    files:
      - api.yml
    ignoreFiles:
      - validator-config.yml
    logLevels:
      root: error
      ibm-schema-description-exists: debug
    ruleset: ./validator-rules.yaml
    produceImpactScore: false
    markdownReport: true
    

    Check document

    Step 4. Check Project Folders and Files

    screenshot 2025 08 31 at 12.57.16 pm

    Step 5. Run Validator on your Local Machine

    lint-openapi -c ./validator-config.yml ./api.yml --errors-only --no-colors
    

    After run the command, It will generate reporting file (md)

    • api-validator-report.md

    Step 6. Add .gitignore file

    node_modules/
    package-lock.json
    package.json
    api-validator-report.md

    Step 7. Setup Github Action

    name: Validate OpenAPI Documentation
    on:
      pull_request:
        branches: [main]
      push:
        branches: [main]
    
    jobs:
      validate:
        name: Validate OpenAPI Documentation
        runs-on: ubuntu-latest
        permissions:
          contents: read
          pull-requests: write
    
        steps:
          - name: Checkout code
            uses: actions/checkout@v4
    
          - name: Setup Node.js
            uses: actions/setup-node@v4
            with:
              node-version: '20'
    
          - name: Install IBM OpenAPI Validator
            run: |
              npm i -g ibm-openapi-validator
              npm i @ibm-cloud/openapi-ruleset
    
          - name: Validate api.yml
            id: validation
            run: |
              if lint-openapi -c ./validator-config.yml ./api.yml --errors-only --no-colors > validation_result.txt 2>&1; then
                echo "validation_status=success" >> $GITHUB_OUTPUT
              else
                echo "validation_status=failed" >> $GITHUB_OUTPUT
              fi
            continue-on-error: true
    
          - name: Comment PR with validation output
            if: github.event_name == 'pull_request'
            uses: actions/github-script@v7
            with:
              script: |
                const fs = require('fs');
                const validationStatus = '${{ steps.validation.outputs.validation_status }}';
                const validationOutput = fs.readFileSync('api-validator-report.md', 'utf8');
                  
                let body = '';
                
                if (validationStatus === 'success') {
                  body = `## ✅ OpenAPI Validation Passed
                  
                  **File**: \`./api.yml\`
                  **Commit**: \`${{ github.sha }}\`
                  
                  ${validationOutput}
                  
                  `;
                } else {
                  body = `## ❌ OpenAPI Validation Failed
                                
                  **File**: \`./api.yml\`
                  **Commit**: \`${{ github.sha }}\`
                  
                  ${validationOutput}
                  
                  `;
                }
                
                // Truncate if too long for GitHub comment limit
                if (body.length > 65000) {
                  body = body.slice(0, 65000) + '\n\n... (truncated due to GitHub comment length limit)';
                }
                
                // Find existing comment and update or create new one
                const { data: comments } = await github.rest.issues.listComments({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: context.issue.number,
                });
                
                const marker = 'OpenAPI Validation';
                const existing = comments.find(c => c.body && c.body.includes(marker));
                
                if (existing) {
                  await github.rest.issues.updateComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    comment_id: existing.id,
                    body
                  });
                } else {
                  await github.rest.issues.createComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    issue_number: context.issue.number,
                    body
                  });
                }
    
    
          - name: Fail job if validation failed
            if: steps.validation.outputs.validation_status == 'failed'
            run: exit 1
    

    Create Github Action yml file

    • .github/workflows/validator.yml

    ⚠️ Check md file name – api-validator-report.md

    const validationOutput = fs.readFileSync('api-validator-report.md', 'utf8');

    Step 8. Check Github Action’s results

    This is the last step, let’s check the results.

    screenshot 2025 08 31 at 1.03.54 pm

    When you create a PR that targets main branch, our GitHub action will run and comment results on your Opened PR.

    screenshot 2025 08 31 at 1.04.19 pm
    screenshot 2025 08 31 at 1.04.27 pm
    screenshot 2025 08 31 at 1.04.34 pm

    IBM OpenAPI Validator provides very detailed information. To fix issues, You can update your OpenAPI Spec file or You can change the rulesets if you don’t want to change your OpenAPI Spec files.

    BTW This validator is really helpful it prevent wrong OpenAPI spec merged into main branch.

    Related Posts