How to fix category url 404’s after category permalink change

RewriteRule ^stores/(.*)/$ /stores/$1-coupon-codes [R=301,NC,L]

You can perhaps use a negative lookahead assertion to exclude URLs that already contain -coupon-codes from being redirected again, thus preventing a redirect-loop (which I assume is what’s happening here).

For example:

RewriteRule ^stores/(?!.*-coupon-codes)(.+)/$ /stores/$1-coupon-codes [R=301,NC,L]

The negative lookahead (?!) causes the pattern match to fail when the URL already contains -coupon-codes.

You’ll need to clear your browser cache and I’d recommend testing first with a 302 (temporary) redirect to avoid potential caching issues.

However, there are some inconsistencies here… you gave the example /stores/walmart (no trailing slash). But the rule you stated (which you say does redirect) would only redirect a URL that includes a trailing slash. If the original URL included a trailing slash then there would be no redirect loop here (since the URL you are redirecting to does not include a trailing slash), unless… something else is appending a trailing slash?

UPDATE: Its working only for URL with slash at the end . Is there any fix for that ? Like appending slash to all URLs of the site if they doesn’t have it already .

Yes, you can make the trailing slash optional in the above regex. However, you also need to make the preceding capturing group (ie. (.+)) non-greedy, ie. (.+?) so that it doesn’t consume the optional trailing slash. For example:

RewriteRule ^stores/(?!.*-coupon-codes)(.+?)/?$ /stores/$1-coupon-codes [R=301,NC,L]

I wouldn’t “append a slash” to all URLs before this redirect, since that would incur another redirect. You could naturally append a slash to the resulting substitution string if that is what you want. ie. /stores/$1-coupon-codes/.

One more issue I figured out while pagination, the URLs are broken : stores/walmart/page/2-coupon-codes, as its appending it like this

You could add a condition to exclude URLs that end with a number in the last path segment. eg. /stores/walmart/page/2 or /<anything>/<number>. For example:

RewriteCond %{REQUEST_URI} !/\d+$
RewriteRule ^stores/(?!.*-coupon-codes)(.+?)/?$ /stores/$1-coupon-codes [R=301,NC,L]

The ! prefix on the CondPattern (2nd argument to the RewriteCond directive) negates the comparison, so it is successful when the regex does not match. In this case, it is successful when the URL-path does not end with a path segment consisting only of digits.

but couldn’t add a valid regex for negative look ahead.

You don’t need to use a negative lookahead, you could use an additional condition (RewriteCond directive) instead. For example:

RewriteCond %{REQUEST_URI} !/\d+$
RewriteCond %{REQUEST_URI} !-coupon-codes/?$ [NC]
RewriteRule ^stores/(.+?)/?$ /stores/$1-coupon-codes [R=301,NC,L]

I didn’t just mention this above, as you seem to already be putting the directives in the correct place, but these directives need to go near the top of the .htaccess file, before the WordPress front-controller.