Skip to content
Rohit Behera

Open to backend roles · available now · Chennai, India

The expensive bugsdon’t crash.

I’m Rohit Behera, a backend engineer. I build Java and Spring Boot services where a retry can’t run twice, and I fix the failures that pass silently in the open-source libraries underneath them, including the ones that feed text to LLMs.

trace · onboarding pipeline · bureau returns 503
  1. 00:00.000infoPOST /api/v1/applications Idempotency-Key: k-CC-3MXP
  2. 00:00.002infofilters: correlation id → bearer auth → rate limit
  3. 00:00.004okone transaction: applicant + application + outbox_events(PENDING) + audit
  4. 00:00.005ok202 Accepted CC-3MXP (no vendor called yet)
  5. 00:02.000okpoller → application.created outbox row PUBLISHED
  6. 00:02.004infodelivered to bureau-check-queue
  7. 00:02.007infostatus guard: RECEIVED ✓
  8. 00:02.127okKYC/AML verified before any bureau spend
  9. 00:02.217warnattempt 1/3: 503 Service Unavailable
  10. 00:02.217warnretry in 1s (exponential backoff)
  11. 00:03.307warnattempt 2/3: 503 Service Unavailable
  12. 00:03.307warnretry in 2s (exponential backoff)
  13. 00:05.487okattempt 3/3: bureau score 718
  14. 00:05.489infopublish bureau.completed (after commit)
  15. 00:05.492infoevaluate: score, debt-to-income, policy version
  16. 00:05.498okAPPROVED → decision_records + status in one tx
  17. 00:05.500infopublish decision.approved (after commit)
  18. 00:05.504infoclaim: DECISION_MADE → CORE_BANKING_PENDING (flushed before the call)
  19. 00:05.744okprovision account → ACCOUNT_CREATED
  20. 00:05.748okmirror row written for the legacy platform
  21. 00:05.751okCOMPLETED
simulated · real retry and breaker configBreak it yourself ↓

Selected work

5 case studies

Hard part first, then the evidence.

  1. 01Personal capstone2026

    Credit-card onboarding pipeline

    A duplicate message here is not wasted work. It is a second hard enquiry on a real person's credit file.

    • Java 17
    • Spring Boot 3.5
    • PostgreSQL 15
    • RabbitMQ
    • Resilience4j
    • Flyway
    • 7 ADRs, including the ones that record what is not built yet
    • 138 tests; one runs the whole pipeline against real Postgres and RabbitMQ
    • 8 Prometheus alert rules, 4 Grafana dashboards
  2. 02Own open source · MIT2026

    splitlint

    The broken chunk still joins back into the original string. Nothing throws; retrieval just gets worse for one language.

    • Python
    • Unicode UAX
    • pytest
    • GitHub Actions
    • 766 of 766 official Unicode grapheme conformance cases pass
    • LangChain's RecursiveCharacterTextSplitter fails 10 of its 12 cases
    • Grew out of an upstream fix in langchain4j
  3. 03Open-source contribution2026

    AST-RAG: code retrieval for AI agents

    Text chunks cannot answer "who calls this?". The graph can, but only if every file is parsed correctly and the parser is safe to share between threads.

    • Python
    • Tree-sitter
    • Neo4j
    • Qdrant
    • SQLite
    • Most of the repository's commit history is mine
    • Parse cache: content-addressed, bounded, two backends
    • Fixed a data race that corrupted results instead of crashing
  4. 04Own open source · MIT2026

    Plumb

    Ask an agent whether the feature works and it will say yes, sincerely, having verified nothing. "Done" was never defined, so the only available answer is a feeling.

    • Python 3.10+
    • Claude Code hooks & skills
    • mypy --strict
    • GitHub Actions
    • Zero runtime dependencies, no network, no telemetry
    • Uninstall removes only files whose hash it recorded
    • Never changes the user's permission mode
  5. 05Professional · a global bank2023–2026

    Legacy service modernization

    Behaviour had to be provably identical before and after, including the parts nobody had documented, because the thing on the other end of the endpoint is somebody's account.

    • Java
    • Spring Boot
    • PostgreSQL
    • REST
    • SOAP
    • Elasticsearch
    • 17+ endpoints migrated, ≈35% of the programme
    • Test coverage raised to 80% by writing the test first
    • Legacy maintenance overhead down ≈25%

Failure lab

interactive

Break the pipeline.

The onboarding service, simulated from its real configuration: retry counts, backoff, breaker thresholds, the outbox poll interval. Pick a failure. Every path ends in a named, audited state, except the one marked as a gap, which is written up with its fix.

Failure lab· simulated from the service’s config

Inject a failure

The bureau stops answering while applications keep arriving.

After 5 failed calls the circuit opens and later applications fail fast instead of piling onto a vendor that is already struggling. Each lands in a visible, audited failed state, not limbo.

In request

  1. Client

    mobile app

  2. Intake API

    filters, validation

  3. Postgres

    app + outbox, 1 tx

After 202

  1. Outbox poller

    every 2s

  2. RabbitMQ

    queue per stage

Stages

  1. KYC

    before any spend

  2. Bureau

    retry + breaker

    circuit closed

  3. Decision

    pure evaluator

  4. Core banking

    claim, then call

  5. Legacy mirror

    vendor row

On nack

  1. Dead letters

    audited failure

Status—

    AI engineering

    retrieval, end to end

    The data path into an LLM.

    Most of my AI work is on the unglamorous side of retrieval: getting text in without losing it, cutting it without corrupting it, indexing structure, measuring whether the right passage comes back, and making agents prove their work. Each stage below links the evidence.

    1. 01Extract

      Turn HTML, PDFs and office files into text.

      Breaks:Content is dropped or garbled, and the document still parses.

      • trafilatura #940mergedText directly after a list, table, quote or code block was dropped from every output format.
      • unstructured #4502mergedDefinition lists were discarded from HTML, so a Sphinx API reference page partitioned to its headings and nothing else.
      • unstructured #4449mergedspaCy ran three times on the same text per element: 88% of partition_html() time in the profile. Now once.
      • docling #3985mergedCSV conversion failed when the first quoted field spanned a newline: the dialect was sniffed from a truncated line.
      • docling #4388reviewMHTML pages with a meta charset were decoded twice, turning non-ASCII text into mojibake.
    2. 02Split

      Cut text into chunks small enough to embed.

      Breaks:A cut lands inside a character. The chunk round-trips and embeds as garbage.

      • splitlintAudits any splitter for it
      • langchain4j #6091mergedThe sentence splitter returned nothing for text with no detectable sentence, so the hierarchical splitter dropped it.
      • langchain4j #6308reviewSplitting by UTF-16 code unit cut surrogate pairs in half, producing chunks that cannot be encoded as UTF-8.
    3. 03Index

      Represent content so it can be found: vectors, and structure where it exists.

      Breaks:Structure is flattened away, or a race corrupts the index without failing.

      • ragedASTs, call graph and vectors for code
    4. 04Retrieve

      Find the passages that answer the question.

      Breaks:A confident answer from the wrong source, and nobody measured how often.

    5. 05Act

      Agents call tools and change things.

      Breaks:The agent reports done without evidence, or the tool wiring quietly points elsewhere.

      • PlumbMakes coding agents prove they are done
      • mastra #20591mergedNine storage adapters declared @mastra/core peer floors older than the exports they import; installs passed, then died on import.
      • chainlit #3013reviewPer-user OAuth for HTTP MCP server connections.
      • hermes-workspace #735reviewThe MCP server list was fetched from the wrong endpoint on both the probe and data paths.

    Upstream

    GitHub API · 2026-11-09

    52 fixes merged. 43 of them never raised an error.

    Fixes sent to libraries I use. Each one arrives with a regression test that fails before the patch and passes after it, because that is the only “I fixed it” a maintainer can check in a minute. The list is generated from the GitHub API; closed-unmerged PRs are left out.

    Filter fixes by class of failure
    Upstream pull requests: 52 merged across 19 projects, 18 in review.
    axios★ 109k#11118mergedCrashOnce an interceptor's handlers array was set to null, every later request threw a TypeError: the length was read before the null check.
    caddy★ 79k#7922mergedWrong resultA proxy URL resolving to a port with no host (http://:80) was accepted, though the code meant to reject it.
    docling★ 70k#3985mergedCrashCSV conversion failed when the first quoted field spanned a newline: the dialect was sniffed from a truncated line.
    docling★ 70k#4391mergedSilent lossAsciiDoc: a titled paragraph and the text after it were dropped.
    docling★ 70k#4394mergedWrong resultAsciiDoc comments were emitted into the document as body text.
    prometheus★ 68k#19324mergedCrashAWS service discovery panicked on Lightsail instances whose optional fields were absent.
    prometheus★ 68k#19396mergedCrashThe same nil-dereference class in ECS task discovery, found by reading the code around the first fix.
    prometheus★ 68k#19486mergedPerformanceThe 14 discovery packages that still ran their tests serially now run them in parallel, checked for shared state first.
    rclone★ 61k#9785mergedSilent lossBox: a source that ended early was uploaded truncated and reported as a success.
    rclone★ 61k#9786mergedSilent lossYandex: the same truncated-upload-reported-as-success bug on a second backend.
    rclone★ 61k#9801mergedSilent lossHuawei Drive: the same truncated-upload bug on a third backend.
    rclone★ 61k#9986mergedCrashValid durations such as 1d12h were rejected by the option parser.
    rclone★ 61k#9987mergedWrong resultA --bwlimit timetable written out of week order applied the wrong limit before the first slot of the week.
    rclone★ 61k#10141mergedWrong resultlib/http: return 416 for ranges --rc-serve and serve restic can't satisfy
    keycloak★ 38k#51356mergedWrong resultCredential-issuer metadata carried empty-string key attestation values that the OID4VCI spec forbids.
    mastra★ 29k#20591mergedCrashNine storage adapters declared @mastra/core peer floors older than the exports they import; installs passed, then died on import.
    goreleaser★ 16k#6752mergedWrong resultEvery generated Homebrew Cask failed brew style: stanza order came from comparing arch strings alphabetically.
    unstructured★ 16k#4449mergedPerformancespaCy ran three times on the same text per element: 88% of partition_html() time in the profile. Now once.
    unstructured★ 16k#4502mergedSilent lossDefinition lists were discarded from HTML, so a Sphinx API reference page partitioned to its headings and nothing else.
    langchain4j★ 13k#6091mergedSilent lossThe sentence splitter returned nothing for text with no detectable sentence, so the hierarchical splitter dropped it.
    txtai★ 13k#1473mergedWrong resultKeep RAG reference ids aligned with texts when empty texts are skipped
    txtai★ 13k#1477mergedWrong resultStore classification labels under the label key for custom label columns
    go-git★ 7.8k#2305mergedCrashPush aborted if any tree anywhere in history held a control character in a path, even one deleted years earlier.
    openevolve★ 7.7k#544mergedWrong resultdrop the fence language tag when a full rewrite falls back to any code block
    openevolve★ 7.7k#546mergedWrong resultonly treat whole lines as SEARCH/REPLACE markers
    gepa★ 7.3k#524mergedWrong resultstrip a fence language tag followed by CRLF or trailing whitespace
    trafilatura★ 7.1k#940mergedSilent lossText directly after a list, table, quote or code block was dropped from every output format.
    trafilatura★ 7.1k#1004mergedWrong resultkeep sitemap locations that are indented inside <loc>
    trafilatura★ 7.1k#1006mergedWrong resultrecognize Python-Markdown codehilite blocks as code
    api-gateway★ 0#2mergedWrong resultEnvoy Gateway with route allow-list, JWT and rate limits
    api-gateway★ 0#4mergedWrong resultTraefik forwards api.dev to the gateway
    api-gateway★ 0#6mergedWrong resultcomms routes to otp-service; backends own their grants
    api-gateway★ 0#8mergedWrong resultthe 429 override rewrites only the gateway's own refusals
    api-gateway★ 0#10mergedWrong resulta valid label with revision-based chart versions
    api-gateway★ 0#12mergedWrong resultroute the customer's answer to their offer
    channel-web★ 0#2mergedWrong resultOTP sign-in, consent, drafts and submission through the gateway
    channel-web★ 0#4mergedWrong resultcustomer journey S01 to S26, English and Hindi
    channel-web★ 0#6mergedWrong resultfailure cases and accessibility evidence
    channel-web★ 0#8mergedWrong resultHTTPS for the web host with Secure __Host- cookies
    channel-web★ 0#10mergedWrong resultpublish the image to GHCR on main
    channel-web★ 0#12mergedWrong resultshow the offer and take the customer's answer after a fresh code
    channel-web★ 0#14mergedWrong resultwait for the step-up code before entering it
    platform-gitops★ 0#2mergedWrong resultFlux with the team services on kind
    platform-gitops★ 0#4mergedWrong resultinclude each chart's values.yaml in valuesFiles
    platform-gitops★ 0#6mergedWrong resultotp-service 696559c
    platform-gitops★ 0#8mergedWrong resultchannel-web 1fc24bd
    platform-gitops★ 0#10mergedWrong resultotp-service 7750b06
    platform-gitops★ 0#12mergedWrong resultthe local CA moves here; Flux is cert-manager's only owner
    sim-comms★ 0#2mergedWrong resultotp-service with signed proofs, limits and simulated phone
    sim-comms★ 0#4mergedWrong resultpublish the image to GHCR on main
    sim-comms★ 0#6mergedWrong resulta verified challenge cannot be resent
    sim-comms★ 0#8mergedWrong resultthe simulated phone answers on its documented address
    docling★ 70k#4388reviewSilent lossMHTML pages with a meta charset were decoded twice, turning non-ASCII text into mojibake.
    prometheus★ 68k#19501reviewCrashRDS discovery dereferenced an optional cluster ARN; a response without it took the process down.
    rclone★ 61k#9787reviewSilent lossGofile and Google Photos: the truncated-upload bug on two more backends.
    rclone★ 61k#9985reviewWrong resultA size passed to the rc API as a JSON number was read as KiB, not bytes: 44040192 asked for 42 MiB and set 42 GiB.
    cobra★ 45k#2473reviewWrong resultHelp text alignment depended on the order commands were attached; cached path lengths went stale on re-parenting.
    aisuite★ 16k#424reviewWrong resultpreserve text across Bedrock response blocks
    unstructured★ 16k#4451reviewSilent loss<details>/<summary> content was discarded from HTML.
    unstructured★ 16k#4503reviewSilent loss<figure> content was discarded from HTML.
    thanos★ 14k#8976reviewCrashRemote-write v2 label references were not bounds-checked, so a malformed client payload panicked the request.
    langchain4j★ 13k#6308reviewSilent lossSplitting by UTF-16 code unit cut surrogate pairs in half, producing chunks that cannot be encoded as UTF-8.
    chainlit★ 13k#3013reviewFeaturePer-user OAuth for HTTP MCP server connections.
    go-git★ 7.8k#2359reviewWrong resultA subsection name containing a newline was written raw, producing a config file neither go-git nor git can read back.
    hermes-workspace★ 6.8k#735reviewWrong resultThe MCP server list was fetched from the wrong endpoint on both the probe and data paths.
    xgrammar★ 2.0k#997reviewWrong resultenforce integer multipleOf with one-sided and large ranges
    llama-agents★ 464#813reviewWrong resultresolve resources safely across concurrent workflow steps
    docling-core★ 298#923reviewWrong resultstop key-value graph rendering at a to_child cycle
    unstructured-ingest★ 110#832reviewWrong resultstop generator_batching_wbytes from dropping items or yielding an empty batch
    gcfg★ 11#16reviewWrong resultRead a leading BOM and continued values the way git does
    loading index…Full retrieval trace →