Module an permission issues I have a custom module called 'products'

I set the access options like so:

function products_permission() {
  return array(
    'access products' =&gt; array(
      'title' =&gt; t('TBYT Products'),
      'description' =&gt; t('Administer TBYT Products'),
    )
  );
}


In the admin I have enabled the module and gave everyone this permission.

Then I defined:

function products_menu() {
  $items = array();

  $items['products/list'] = array(
    'type' =&gt; MENU_CALLBACK,

    'title' =&gt; 'Enjoy shopping',

    'page callback' =&gt; 'html_list_products',      

    'access arguments' =&gt; array('access products')
  );

   return $items;
}


This works fine, however I need to now make the URI dynamic, so I added something like:

  $items['products/list/%/%'] = array(


I also added the appropriate page arguments:

'page arguments' =&gt; array(2, 3),


However when I rebuild the cache and I access:

products/list/23

it works fine but when I access 

products/list

I get:

The requested page "/products/list" could not be found.
Addressing the (correct) conclusions you've come to in the comments...

Yes, you'll see some repetition in Drupal 6/7. Drupal 8 uses Symfony routes so things get better, but it's not stable yet.

For your specific case you don't have to have 3 separate menu items (although for the sake of clarity it might make sense). You can define a single list page like so

function products_menu() {
  $items = array();

  $items['products/list'] = array(
    'type' =&gt; MENU_CALLBACK,
    'title' =&gt; 'Enjoy shopping',
    'page callback' =&gt; 'html_list_products',      
    'access arguments' =&gt; array('access products')
  );

  return $items;
}


And define your page callback like so

function html_list_products($arg1 = NULL, $arg2 = NULL) {
  if ($arg1 &amp;&amp; arg2) {
    // Both args were provided.
  }
  elseif ($arg1) {
    // Just the first arg was provided.
  }
  else {
    // No args were provided.
  }
}


Drupal will always pass along the remaining slash-separated parts of the request URL to the callback function, so with the above method if you visit /products/list/1/2, $arg1 and $arg2 will be populated respectively. Similarly if you visit /products/list/1 just $arg1 will be populated, and for /products/list, neither will be.