Senior backend engineer in Valencia, Spain. I build PHP/Laravel services: APIs, queues and background jobs, SSO integrations, and the systems that keep releases running reliably.
In open source, I look for bugs in the tools I use and report them with a reproduction, a failing test and, where I can, a fix.
- Hindsight (~60k stars, no outside pull requests): five bug reports with reproductions, four with patches and one with a failing test; the maintainer fixed all five, each within four days. For #6337, concurrent retries that stored facts twice, the fix keeps my failing test, and the merged commit lists me as co-author. #6335 was fixed in #6408, which follows my patch, and #6409; #6280 was fixed with thanks; #6231 shipped in v1.2.0; #6235. Open, each with failing tests and a patch: #6444 (the follow-ups #6408 left); #6450 (re-running the new re-embed command with another model of the same size left vectors from both); #6452 (retaining a document again undid the tags an extension had given its memories).
- Symfony: fourteen merged bug fixes in Messenger, Lock, Semaphore, RateLimiter and Scheduler, each with a failing test and a reproduction:
- #68021 RateLimiter: the sliding window counted reservations for later windows as hits that had already happened, so workers waiting for a reservation went over the limit: with 10 per minute, 50 workers got 60.
- #67993 Messenger: with
--fetch-size,--keepalivecovered only the message being handled, so the rest of the batch went to another worker and was handled twice (reproduced with two workers on PostgreSQL). - #68020 Redis transport: after a failed read, a worker with the new cooldown strategy kept running but never read again the messages Redis had already delivered to it.
- #68011 Semaphore: once a slot of a
LockStoresemaphore had expired and another process took it,acquire()still returnedtrue, so two processes held a semaphore limited to one. - #67994 Lock: once the PostgreSQL session was lost,
DoctrineDbalPostgreSqlStorenever gave that lock to the process again until it restarted. - #68029 Lock: with a Predis client created from a DSN, Redis errors were taken for a success, so after a failover two processes could hold the same lock.
- #68025 Scheduler: after a restart, a cron task without a timezone moved to the timezone of the task that ran last, so a nightly 02:00 UTC job ran at 07:00.
- #68019 RateLimiter: the token bucket counted the wait from now instead of from its last refill, so
retryAfterwas up to a full interval late. - Also merged:
CompoundLimiteraccepting a hit that one of its limiters refused (#68016), a Redis keepalive check comparing milliseconds with seconds (#68017), MongoDBisAcquired()failing before the firstacquire()(#68027), Memcached locks longer than 30 days expiring at once (#68028), a Scheduler worker crashing when its lock expired during a long run (#68030),'P1M'intervals running every 31 days (#68026). - Under review: #68022, a follow-up the maintainer asked for.
- hindsight-php: long-term memory for Laravel AI SDK agents on the open-source Hindsight server. It stores memories through the Laravel queue, and a retry never stores one twice;
Hindsight::fake()makes it testable, and v0.3 ships a Laravel Boost skill for coding agents. CI runs against real Hindsight 1.1 and 1.2 servers. - AI libraries for PHP: openai-php/client#833 (merged): streaming a response that calls a custom tool failed with
UnhandledMatchError, because the streaming parser kept its own list of output types and it fell behind the onecreate()uses. Under review: symfony/ai#2893:#[Assert\Regex]turned into a JSON schema pattern that let more through than the constraint (/^[a-z]+$/became[a-z]+), so tool and structured-output schemas allowed values the validator then rejected. prism-php/prism#1054 fixes #990: DeepSeek and Perplexity streams arrived all at once at the end, and any stream longer than the 30 s request timeout failed. symfony/ai#2895: the Redis vector store returned only about half of the documents a query found, 3 of 5 with the default limit. symfony/ai#2896: an agent whose tool returned nothing (void) failed with aTypeErrorafter the tool had already run. symfony/ai#2897:drop()on the Neo4j store deleted every node in the database, not only the store's. - MCP PHP SDK (official SDK, under review): #707: a stdio server handled a message larger than the pipe buffer in parts, so a tool call with 100 KB of arguments got parse errors and never an answer, and the client timed out.
- php-amqplib#1248 (under review): fixes #1227 in the main PHP AMQP client. The heartbeat check counted the client's own writes, so a consumer whose network dropped never noticed. Reproduced with toxiproxy; opt-in, so proxies that send heartbeats differently (AMQProxy before 3.2.1) aren't affected. #1249 fixes #1243: the error handler around socket calls turned unrelated deprecations into exceptions. In the async client bunny, #237 (under review): after one failed connect, every later
channel()call hung forever. - Predis (under review): #1755: when Redis rejected a command queued in a transaction built with the fluent interface (OOM,
READONLYafter a failover), the connection stayed inMULTI, so every later command on it was queued and never ran. With persistent connections, the next request on that PHP-FPM worker got the same socket. #1756: once a transaction had sentWATCHorMULTI, a lost connection made Predis retry the command on a new connection, outside the transaction. With Sentinel this happened with default options, and a check-and-set transaction then overwrote a change another client made afterWATCH. - laravel-queue-lab: reproducible experiments with Laravel workers, rerun in CI every week: jobs that run twice after RabbitMQ's acknowledgement timeout, a frozen broker that
queue:workdoesn't notice,rabbitmq:consumeignoring SIGTERM in maintenance mode, and two Laravel 13 bugs: debouncemaxWaitthat didn't force a single run in 60 s of steady dispatches, and a timeout counted twice againstmaxExceptions(v0.5). - vyuldashev/laravel-queue-rabbitmq#680 (under review): fixes
rabbitmq:consumefor the Worker API changes in Laravel 11.52+, 12.59+ and 13.x. With the released v15.0.2 it doesn't process jobs on Laravel 13.31+. #684 builds on it: in maintenance mode a pausedrabbitmq:consumeignored SIGTERM,queue:restartand--max-time. - Queue reliability checks for other people's issues: an analysis of #664 (the reported hang is really redelivery), an independent check of PR #674, and measurements for php-amqplib#1227.
- Queues and workers: retries, connection failures, recovery, and safe restarts.
- Release reliability: what happens when old and new application versions run together.
- AI-assisted development: understanding, testing, and reviewing the code that coding tools produce.
PHP · Laravel · PostgreSQL · Redis · RabbitMQ · Docker · Kubernetes