Get started Dashboard
Troubleshooting ·

ChangeMessageVisibility: troubleshoot SQS message timeout errors

Explore with AI

ChangeMessageVisibility sets a new timeout for an in-flight SQS message using its receipt handle. The countdown starts when the change is made, replacing the old deadline; it does not reset the 12-hour maximum from ReceiveMessage. Use the latest handle, extend before expiry, and inspect per-entry failures for batch changes.

Ongoing incident? Our founding team can help you get your AWS app back up.
On this page

ChangeMessageVisibility changes how long an in-flight Amazon SQS message stays hidden from other consumers. The API reference requires a queue URL, the receipt handle from ReceiveMessage, and a timeout in seconds. Extend active work before visibility expires, or set zero to release it; neither operation deletes the message.

When a change fails, inspect the error and receipt-handle lifecycle before increasing the timeout. For Lambda consumers, first inspect the source queue and event source mapping rather than importing a standalone worker’s heartbeat strategy.

Meaning: a new countdown, not added time

A received SQS message is hidden for the queue’s visibility timeout or the timeout supplied to ReceiveMessage. The queue default is 30 seconds. It stays in flight until deletion or the end of visibility.

ChangeMessageVisibility starts a new countdown immediately. AWS’s example receives a message with a 60-second timeout, waits 15 seconds, then changes visibility to ten seconds. It becomes eligible for visibility at about second 25, not second 70. A shorter change can therefore release work earlier than the old deadline.

The valid duration range is 0 to 43,200 seconds. A per-message change is temporary; the next receive uses the queue’s original setting unless that receive supplies an override. It does not permanently rewrite queue configuration or the message’s default timeout.

There is also a 12-hour maximum from the ReceiveMessage request. Renewals do not reset it. Asking for the full 43,200 seconds after the response arrives may already exceed remaining time because the receive consumed part of that budget. SQS fails the request rather than clamping it to what remains.

Visibility reduces concurrent delivery; it is not an exactly-once processing guarantee. Keep business operations idempotent even when every renewal returns successfully.

Causes to test when a change fails

These are diagnostic possibilities, not prevalence claims:

  • The worker supplied a message ID instead of a receipt handle, or reused a handle from an older receive.
  • Visibility expired or another path deleted the message before the change reached SQS.
  • The requested timeout exceeds remaining time in the 12-hour budget.
  • The URL points to the wrong queue, account or Region, or the queue was deleted.
  • Frequent heartbeat requests reached a request-rate limit.
  • Authorization or request signing is wrong.
  • A batch returned failed entries that the worker ignored because HTTP status was 200.

Capture queue ARN, message ID, receive time, requested duration, AWS request ID and exact error code. Avoid publishing receipt handles and message bodies in broad logs. They are not useful high-cardinality metric dimensions and can expose application data.

Confirm queue configuration and handle ownership

Start with read operations in the intended account and Region:

export AWS_REGION=us-east-1
QUEUE_URL=https://sqs.us-east-1.amazonaws.com/123456789012/orders
aws sts get-caller-identity
aws sqs get-queue-attributes --queue-url "$QUEUE_URL" \
  --attribute-names QueueArn VisibilityTimeout RedrivePolicy \
    ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible

In SQS → Queues → your queue → Configuration, inspect visibility timeout and redrive policy. In Monitoring, look at visible and in-flight backlog and message age. Those are queue-level indicators, not proof that a particular handle’s renewal succeeded.

Use a disposable queue or known test message for reproduction. Receiving a live production message changes its visibility and can compete with real workers:

aws sqs receive-message --queue-url "$QUEUE_URL" \
  --max-number-of-messages 1 --wait-time-seconds 10 \
  --visibility-timeout 120

# Set this to ReceiptHandle from the response, not MessageId.
aws sqs change-message-visibility --queue-url "$QUEUE_URL" \
  --receipt-handle "$RECEIPT_HANDLE" --visibility-timeout 120

The successful response is HTTP 200 with an empty body, so the CLI may print nothing. It does not return a new receipt handle. Continue tracking the handle for that receive; a subsequent receive has its own handle.

Exact errors and the next check

The operation-specific error table lists these errors with HTTP 400:

ErrorMeaning and confirmation
MessageNotInflightMessage is not in flight; inspect expiry, deletion and active ownership.
ReceiptHandleIsInvalidHandle is invalid; check the latest receive and corresponding queue.
QueueDoesNotExistInspect exact URL, Region, account and deletion history.
RequestThrottledRequest rate exceeded permitted limits; inspect heartbeat rate and batching.
InvalidSecurityRequest lacked HTTPS or SigV4 signing; use the SDK/CLI signing path.
InvalidAddressSpecified address/ID is invalid; check the target.
UnsupportedOperationUnsupported operation; inspect request and service response.

Common authorization errors can occur as well. Preserve the actual SDK error instead of reducing every failure to “timeout.” The reference says exceeding remaining visibility time returns an error but does not establish one universal named exception for every such request. Diagnose that case with elapsed time, requested duration and returned message.

Do not retry a permanently invalid or expired handle forever. Once the worker cannot establish a successful renewal, concurrent processing is possible. Stop or checkpoint where the application supports it, and use idempotency to prevent duplicated irreversible effects.

Fix standalone consumers with bounded heartbeats

AWS’s timely-processing guidance suggests an initial two-minute visibility timeout and extensions every minute for work of uncertain length. It is an example, not a universal rate. Leave margin for latency and retries, measure elapsed time monotonically from receiving, and stop extending abandoned or completed jobs.

Boto3 uses these real renewal and deletion operations:

import boto3
from botocore.config import Config

sqs = boto3.client(
    "sqs",
    config=Config(retries={"mode": "standard", "total_max_attempts": 4}),
)

def extend_message(queue_url, receipt_handle, seconds):
    # Caller checks remaining receive budget and keeps a safety margin.
    return sqs.change_message_visibility(
        QueueUrl=queue_url, ReceiptHandle=receipt_handle,
        VisibilityTimeout=seconds,
    )

def delete_completed_message(queue_url, receipt_handle):
    return sqs.delete_message(
        QueueUrl=queue_url, ReceiptHandle=receipt_handle,
    )

These helpers do not implement a complete scheduler. The worker must observe heartbeat failures, serialize renewal completion with shutdown/deletion and enforce an overall processing deadline. A background task that fails silently has not renewed anything.

Delete after the business operation completes durably. Repeatedly renewing a stuck job delays retry and holds in-flight capacity. For work longer than 12 hours, split/checkpoint the job or use another workflow such as Step Functions, as AWS recommends.

Batch extensions: inspect every entry

ChangeMessageVisibilityBatch accepts at most ten entries, each with a distinct batch ID, current receipt handle and timeout. HTTP 200 can include both successful and unsuccessful entries:

result = sqs.change_message_visibility_batch(
    QueueUrl=queue_url,
    Entries=[
        {"Id": "job-1", "ReceiptHandle": handle_one, "VisibilityTimeout": 120},
        {"Id": "job-2", "ReceiptHandle": handle_two, "VisibilityTimeout": 120},
    ],
)
for failure in result.get("Failed", []):
    print(failure["Id"], failure["Code"], failure["SenderFault"])
    # Retry or stop this entry according to its error and remaining budget.

Batch request errors include BatchEntryIdsNotDistinct, EmptyBatchRequest, InvalidBatchEntryId and TooManyEntriesInBatchRequest. Fix malformed input instead of retrying it unchanged. For transient failures, retry only appropriate failed entries and recompute remaining time. Do not reset a failed entry’s local deadline because another entry succeeded.

Lambda interplay: source queue settings first

Lambda polls SQS and invokes synchronous batches. Its configuration guide recommends queue visibility of at least six times function timeout plus MaximumBatchingWindowInSeconds when a batch window is used. Lambda validates that function timeout is no greater than queue visibility when creating or updating a mapping.

A 30-second function timeout and five-second batch window give a recommended starting visibility of 185 seconds:

aws sqs set-queue-attributes --queue-url "$QUEUE_URL" \
  --attributes VisibilityTimeout=185
aws lambda list-event-source-mappings --function-name orders-worker

Calculate from the actual function and window. Inspect mapping state, batch size, concurrency and FunctionResponseTypes. Partial batch responses let Lambda retry only failed records instead of successful records from the same batch. Configure the DLQ on the SQS source queue; AsyncEventsDropped belongs to Lambda’s separate internal async invocation queue.

A handler that calls this API needs explicit sqs:ChangeMessageVisibility permission. The call does not extend Lambda execution time. Smaller work units and bounded downstream calls are better than preserving a message for a handler that cannot finish it.

Verify recovery and prevent repeats

In a test queue, renew a received message, verify it remains unavailable during the intended window, then allow expiry and confirm later redelivery. Delete a completed test message and check the durable business result. Exercise a deliberately invalid batch entry so your failure handling is verified, not merely the happy path.

Track processing duration, renewal failures, receive counts and DLQ backlog. The LambdaInsightsExtension guide helps investigate function CPU and memory pressure. Keep investigation reads separate from operational writes such as receives and renewals.

Polylane’s AWS integration connects it to AWS, so it can see when a queue backs up and work out why. The AWS overview has more. Visibility deadlines and duplicate-handling guarantees still belong to the consumer design.

Running on AWS? See how Polylane monitors AWS in production.

Common questions.

Does ChangeMessageVisibility add time to the existing deadline?

No. The requested duration starts from the change and replaces the current countdown. Setting 120 seconds means about 120 seconds from the change, not 120 seconds after the old deadline.

Can I extend a message beyond 12 hours?

No. The maximum is measured from when SQS receives the ReceiveMessage request. Extensions do not reset it, and a request beyond the remaining time fails rather than being automatically shortened.

Does HTTP 200 mean every batch entry succeeded?

No. ChangeMessageVisibilityBatch can return HTTP 200 with a non-empty Failed array. Inspect each entry's Id, Code, Message and SenderFault before treating that message as extended.

Should a Lambda SQS handler extend messages automatically?

Start with queue visibility and event source mapping configuration. AWS recommends at least six times the function timeout plus the batch window. An extension cannot make the handler run past its function timeout.

Does setting visibility to zero delete the message?

No. It ends current visibility so the message can be received again. DeleteMessage is a separate operation used after processing completes durably.

Sources (5)

  1. ChangeMessageVisibility API reference
  2. ChangeMessageVisibilityBatch API reference
  3. Timely processing and the visibility limit
  4. Lambda SQS mapping configuration
  5. Polylane AWS integration

About the author

Boris Tane

Founder of Polylane

Boris Tane is the founder of Polylane. He previously founded Baselime, observability for the future of the cloud, which Cloudflare acquired. At Cloudflare he built and led the Workers observability team.

Related

Nobody should be on-call. Polylane watches your infra, finds what broke, and writes the fix.

Get started for free